[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [orbot/master] first commit of new OrbotVPN integration into Orbot
commit e45991899a015798973be125ddc51e506ac09d6a
Author: Nathan Freitas <nathan@xxxxxxxxxxx>
Date: Tue Oct 21 12:30:13 2014 -0400
first commit of new OrbotVPN integration into Orbot
---
.gitmodules | 3 +
AndroidManifest.xml | 8 +
external/badvpn | 1 +
external/tor | 2 +-
res/menu/main.xml | 3 +
src/com/runjva/sourceforge/jsocks/main/SOCKS.java | 269 ++++++++
.../runjva/sourceforge/jsocks/main/SocksEcho.gif | Bin 0 -> 926 bytes
.../jsocks/protocol/Authentication.java | 35 +
.../jsocks/protocol/AuthenticationNone.java | 22 +
.../sourceforge/jsocks/protocol/InetRange.java | 492 ++++++++++++++
.../sourceforge/jsocks/protocol/ProxyMessage.java | 118 ++++
.../sourceforge/jsocks/protocol/ProxyServer.java | 669 ++++++++++++++++++++
.../sourceforge/jsocks/protocol/Socks4Message.java | 167 +++++
.../sourceforge/jsocks/protocol/Socks4Proxy.java | 144 +++++
.../jsocks/protocol/Socks5DatagramSocket.java | 485 ++++++++++++++
.../sourceforge/jsocks/protocol/Socks5Message.java | 330 ++++++++++
.../sourceforge/jsocks/protocol/Socks5Proxy.java | 295 +++++++++
.../jsocks/protocol/SocksException.java | 111 ++++
.../jsocks/protocol/SocksProxyBase.java | 543 ++++++++++++++++
.../jsocks/protocol/SocksServerSocket.java | 238 +++++++
.../sourceforge/jsocks/protocol/SocksSocket.java | 389 ++++++++++++
.../jsocks/protocol/UDPEncapsulation.java | 33 +
.../jsocks/protocol/UDPRelayServer.java | 231 +++++++
.../protocol/UserPasswordAuthentication.java | 91 +++
.../runjva/sourceforge/jsocks/server/Ident.java | 176 +++++
.../jsocks/server/IdentAuthenticator.java | 182 ++++++
.../jsocks/server/ServerAuthenticator.java | 126 ++++
.../jsocks/server/ServerAuthenticatorBase.java | 187 ++++++
.../jsocks/server/ServerAuthenticatorNone.java | 16 +
.../jsocks/server/UserPasswordAuthenticator.java | 82 +++
.../sourceforge/jsocks/server/UserValidation.java | 24 +
src/org/torproject/android/Orbot.java | 27 +-
.../torproject/android/vpn/OrbotVpnService.java | 239 +++++++
src/org/torproject/android/vpn/Tun2Socks.java | 124 ++++
34 files changed, 5858 insertions(+), 4 deletions(-)
diff --git a/.gitmodules b/.gitmodules
index caa21c3..9041ca4 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -29,3 +29,6 @@
[submodule "external/polipo"]
path = external/polipo
url = https://github.com/jech/polipo.git
+[submodule "external/badvpn"]
+ path = external/badvpn
+ url = https://github.com/ambrop72/badvpn.git
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 75b1c32..22a6e6b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -101,6 +101,14 @@
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
+
+
+ <service android:name="org.torproject.android.vpn.OrbotVpnService"
+ android:permission="android.permission.BIND_VPN_SERVICE">
+ <intent-filter>
+ <action android:name="android.net.VpnService"/>
+ </intent-filter>
+ </service>
</application>
diff --git a/external/badvpn b/external/badvpn
new file mode 160000
index 0000000..5153894
--- /dev/null
+++ b/external/badvpn
@@ -0,0 +1 @@
+Subproject commit 5153894f4375d7a0e43d0b60c1b759543e6b383a
diff --git a/external/tor b/external/tor
index 40233ca..a64f3ab 160000
--- a/external/tor
+++ b/external/tor
@@ -1 +1 @@
-Subproject commit 40233cadbbbf77214913db818a1458c6ddd14a9f
+Subproject commit a64f3ab3ee5c433cc1f046a7e26df7a49e308e4c
diff --git a/res/menu/main.xml b/res/menu/main.xml
index 9954339..4d2ac1a 100644
--- a/res/menu/main.xml
+++ b/res/menu/main.xml
@@ -73,6 +73,9 @@
/>
+ <item android:id="@+id/menu_vpn"
+ android:title="start VPN"
+ yourapp:showAsAction="never"/>
<!--
<item android:id="@+id/menu_diag"
android:title="Test Mode"
diff --git a/src/com/runjva/sourceforge/jsocks/main/SOCKS.java b/src/com/runjva/sourceforge/jsocks/main/SOCKS.java
new file mode 100644
index 0000000..d1012fd
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/main/SOCKS.java
@@ -0,0 +1,269 @@
+package com.runjva.sourceforge.jsocks.main;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.runjva.sourceforge.jsocks.protocol.InetRange;
+import com.runjva.sourceforge.jsocks.protocol.ProxyServer;
+import com.runjva.sourceforge.jsocks.protocol.SocksProxyBase;
+import com.runjva.sourceforge.jsocks.server.IdentAuthenticator;
+
+public class SOCKS {
+
+ private static final int DEFAULT_LISTENING_PORT = 1080;
+ final private static Logger log = LoggerFactory.getLogger(SOCKS.class);
+
+ static public void usage() {
+ System.out.println("Usage: java SOCKS [inifile1 inifile2 ...]\n"
+ + "If none inifile is given, uses socks.properties.\n");
+ }
+
+ static public void main(String[] args) {
+
+ String[] file_names;
+ int port = DEFAULT_LISTENING_PORT;
+ String logFile = null;
+ String host = null;
+
+ final IdentAuthenticator auth = new IdentAuthenticator();
+
+ InetAddress localIP = null;
+
+ if (args.length == 0) {
+ file_names = new String[] { "socks.properties" };
+ } else {
+ file_names = args;
+ }
+
+ inform("Loading properties");
+ for (int i = 0; i < file_names.length; ++i) {
+
+ inform("Reading file " + file_names[i]);
+
+ final Properties pr = loadProperties(file_names[i]);
+ if (pr == null) {
+ System.err.println("Loading of properties from "
+ + file_names[i] + "failed.");
+ usage();
+ return;
+ }
+ if (!addAuth(auth, pr)) {
+ System.err.println("Error in file " + file_names[i] + ".");
+ usage();
+ return;
+ }
+ // First file should contain all global settings,
+ // like port and host and log.
+ if (i == 0) {
+ final String port_s = (String) pr.get("port");
+ if (port_s != null) {
+ try {
+ port = Integer.parseInt(port_s);
+ } catch (final NumberFormatException nfe) {
+ System.err.println("Can't parse port: " + port_s);
+ return;
+ }
+ }
+
+ serverInit(pr);
+ logFile = (String) pr.get("log");
+ host = (String) pr.get("host");
+ }
+
+ // inform("Props:"+pr);
+ }
+
+ if (logFile != null) {
+ System.err.println("log property not supported anymore.");
+ }
+ if (host != null) {
+ try {
+ localIP = InetAddress.getByName(host);
+ } catch (final UnknownHostException uhe) {
+ System.err.println("Can't resolve local ip: " + host);
+ return;
+ }
+ }
+
+ inform("Using Ident Authentication scheme: " + auth);
+ final ProxyServer server = new ProxyServer(auth);
+ server.start(port, 5, localIP);
+ }
+
+ static Properties loadProperties(String file_name) {
+
+ final Properties pr = new Properties();
+
+ try {
+ final InputStream fin = new FileInputStream(file_name);
+ pr.load(fin);
+ fin.close();
+ } catch (final IOException ioe) {
+ return null;
+ }
+ return pr;
+ }
+
+ static boolean addAuth(IdentAuthenticator ident, Properties pr) {
+
+ InetRange irange;
+
+ final String range = (String) pr.get("range");
+ if (range == null) {
+ return false;
+ }
+ irange = parseInetRange(range);
+
+ final String users = (String) pr.get("users");
+
+ if (users == null) {
+ ident.add(irange, null);
+ return true;
+ }
+
+ final Hashtable<String, String> uhash = new Hashtable<String, String>();
+
+ final StringTokenizer st = new StringTokenizer(users, ";");
+ while (st.hasMoreTokens()) {
+ uhash.put(st.nextToken(), "");
+ }
+
+ ident.add(irange, uhash);
+ return true;
+ }
+
+ /**
+ * Does server initialisation.
+ */
+ static void serverInit(Properties props) {
+ int val;
+ val = readInt(props, "iddleTimeout");
+ if (val >= 0) {
+ ProxyServer.setIddleTimeout(val);
+ inform("Setting iddle timeout to " + val + " ms.");
+ }
+ val = readInt(props, "acceptTimeout");
+ if (val >= 0) {
+ ProxyServer.setAcceptTimeout(val);
+ inform("Setting accept timeout to " + val + " ms.");
+ }
+ val = readInt(props, "udpTimeout");
+ if (val >= 0) {
+ ProxyServer.setUDPTimeout(val);
+ inform("Setting udp timeout to " + val + " ms.");
+ }
+
+ val = readInt(props, "datagramSize");
+ if (val >= 0) {
+ ProxyServer.setDatagramSize(val);
+ inform("Setting datagram size to " + val + " bytes.");
+ }
+
+ proxyInit(props);
+
+ }
+
+ /**
+ * Initialises proxy, if any specified.
+ */
+ static void proxyInit(Properties props) {
+ String proxy_list;
+ SocksProxyBase proxy = null;
+ StringTokenizer st;
+
+ proxy_list = (String) props.get("proxy");
+ if (proxy_list == null) {
+ return;
+ }
+
+ st = new StringTokenizer(proxy_list, ";");
+ while (st.hasMoreTokens()) {
+ final String proxy_entry = st.nextToken();
+
+ final SocksProxyBase p = SocksProxyBase.parseProxy(proxy_entry);
+
+ if (p == null) {
+ exit("Can't parse proxy entry:" + proxy_entry);
+ }
+
+ inform("Adding Proxy:" + p);
+
+ if (proxy != null) {
+ p.setChainProxy(proxy);
+ }
+
+ proxy = p;
+
+ }
+ if (proxy == null) {
+ return; // Empty list
+ }
+
+ final String direct_hosts = (String) props.get("directHosts");
+ if (direct_hosts != null) {
+ final InetRange ir = parseInetRange(direct_hosts);
+ inform("Setting direct hosts:" + ir);
+ proxy.setDirect(ir);
+ }
+
+ ProxyServer.setProxy(proxy);
+ }
+
+ /**
+ * Inits range from the string of semicolon separated ranges.
+ */
+ static InetRange parseInetRange(String source) {
+ final InetRange irange = new InetRange();
+
+ final StringTokenizer st = new StringTokenizer(source, ";");
+ while (st.hasMoreTokens()) {
+ irange.add(st.nextToken());
+ }
+
+ return irange;
+ }
+
+ /**
+ * Integer representaion of the property named name, or -1 if one is not
+ * found.
+ */
+ static int readInt(Properties props, String name) {
+ int result = -1;
+ final String val = (String) props.get(name);
+ if (val == null) {
+ return -1;
+ }
+ final StringTokenizer st = new StringTokenizer(val);
+ if (!st.hasMoreElements()) {
+ return -1;
+ }
+ try {
+ result = Integer.parseInt(st.nextToken());
+ } catch (final NumberFormatException nfe) {
+ inform("Bad value for " + name + ":" + val);
+ }
+ return result;
+ }
+
+ // Display functions
+ // /////////////////
+
+ static void inform(String s) {
+ log.info(s);
+ }
+
+ static void exit(String msg) {
+ System.err.println("Error:" + msg);
+ System.err.println("Aborting operation");
+ System.exit(0);
+ }
+}
diff --git a/src/com/runjva/sourceforge/jsocks/main/SocksEcho.gif b/src/com/runjva/sourceforge/jsocks/main/SocksEcho.gif
new file mode 100644
index 0000000..701d39a
Binary files /dev/null and b/src/com/runjva/sourceforge/jsocks/main/SocksEcho.gif differ
diff --git a/src/com/runjva/sourceforge/jsocks/protocol/Authentication.java b/src/com/runjva/sourceforge/jsocks/protocol/Authentication.java
new file mode 100644
index 0000000..7bc58d9
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/Authentication.java
@@ -0,0 +1,35 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+/**
+ * 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/com/runjva/sourceforge/jsocks/protocol/AuthenticationNone.java b/src/com/runjva/sourceforge/jsocks/protocol/AuthenticationNone.java
new file mode 100644
index 0000000..e682154
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/AuthenticationNone.java
@@ -0,0 +1,22 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * SOCKS5 none authentication. Dummy class does almost nothing.
+ */
+public class AuthenticationNone implements Authentication {
+
+ public Object[] doSocksAuthentication(final int methodId,
+ final java.net.Socket proxySocket) throws java.io.IOException {
+
+ if (methodId != 0) {
+ return null;
+ }
+
+ InputStream in = proxySocket.getInputStream();
+ OutputStream out = proxySocket.getOutputStream();
+ return new Object[] { in, out };
+ }
+}
diff --git a/src/com/runjva/sourceforge/jsocks/protocol/InetRange.java b/src/com/runjva/sourceforge/jsocks/protocol/InetRange.java
new file mode 100644
index 0000000..fae1358
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/InetRange.java
@@ -0,0 +1,492 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * Class InetRange provides the means of defining the range of inetaddresses.
+ * It's used by Proxy class to store and look up addresses of machines, that
+ * should be contacted directly rather then through the proxy.
+ * <P>
+ * InetRange provides several methods to add either standalone addresses, or
+ * ranges (e.g. 100.200.300.0:100.200.300.255, which covers all addresses on on
+ * someones local network). It also provides methods for checking wether given
+ * address is in this range. Any number of ranges and standalone addresses can
+ * be added to the range.
+ */
+public class InetRange implements Cloneable {
+
+ Hashtable<String, Object[]> host_names;
+ Vector<Object[]> all;
+ Vector<String> end_names;
+
+ boolean useSeparateThread = true;
+
+ /**
+ * Creates the empty range.
+ */
+ public InetRange() {
+ all = new Vector<Object[]>();
+ host_names = new Hashtable<String, Object[]>();
+ end_names = new Vector<String>();
+ }
+
+ /**
+ * Adds another host or range to this range. The String can be one of those:
+ * <UL>
+ * <li>Host name. eg.(Athena.myhost.com or 45.54.56.65)
+ *
+ * <li>Range in the form .myhost.net.au <BR>
+ * In which case anything that ends with .myhost.net.au will be considered
+ * in the range.
+ *
+ * <li>Range in the form ddd.ddd.ddd. <BR>
+ * This will be treated as range ddd.ddd.ddd.0 to ddd.ddd.ddd.255. It is not
+ * necessary to specify 3 first bytes you can use just one or two. For
+ * example 130. will cover address between 130.0.0.0 and 13.255.255.255.
+ *
+ * <li>Range in the form host_from[: \t\n\r\f]host_to. <br>
+ * That is two hostnames or ips separated by either whitespace or colon.
+ * </UL>
+ */
+ public synchronized boolean add(final String s0) {
+ if (s0 == null) {
+ return false;
+ }
+
+ String s = s0.trim();
+ if (s.length() == 0) {
+ return false;
+ }
+
+ Object[] entry;
+
+ if (s.endsWith(".")) {
+ // thing like: 111.222.33.
+ // it is being treated as range 111.222.33.000 - 111.222.33.255
+
+ final int[] addr = ip2intarray(s);
+ long from, to;
+ from = to = 0;
+
+ if (addr == null) {
+ return false;
+ }
+ for (int i = 0; i < 4; i++) {
+ if (addr[i] >= 0) {
+ from += (((long) addr[i]) << 8 * (3 - i));
+ } else {
+ to = from;
+ while (i < 4) {
+ to += 255l << 8 * (3 - i++);
+ }
+ break;
+ }
+ }
+ entry = new Object[] { s, null, new Long(from), new Long(to) };
+ all.addElement(entry);
+
+ } else if (s.startsWith(".")) {
+ // Thing like: .myhost.com
+
+ end_names.addElement(s);
+ all.addElement(new Object[] { s, null, null, null });
+ } else {
+ final StringTokenizer tokens = new StringTokenizer(s, " \t\r\n\f:");
+ if (tokens.countTokens() > 1) {
+ entry = new Object[] { s, null, null, null };
+ resolve(entry, tokens.nextToken(), tokens.nextToken());
+ all.addElement(entry);
+ } else {
+ entry = new Object[] { s, null, null, null };
+ all.addElement(entry);
+ host_names.put(s, entry);
+ resolve(entry);
+ }
+
+ }
+
+ return true;
+ }
+
+ /**
+ * Adds another ip for this range.
+ *
+ * @param ip
+ * IP os the host which should be added to this range.
+ */
+ public synchronized void add(final InetAddress ip) {
+ long from, to;
+ from = to = ip2long(ip);
+ all.addElement(new Object[] { ip.getHostName(), ip, new Long(from),
+ new Long(to) });
+ }
+
+ /**
+ * Adds another range of ips for this range.Any host with ip address greater
+ * than or equal to the address of from and smaller than or equal to the
+ * address of to will be included in the range.
+ *
+ * @param from
+ * IP from where range starts(including).
+ * @param to
+ * IP where range ends(including).
+ */
+ public synchronized void add(final InetAddress from, final InetAddress to) {
+ all.addElement(new Object[] {
+ from.getHostAddress() + ":" + to.getHostAddress(), null,
+ new Long(ip2long(from)), new Long(ip2long(to)) });
+ }
+
+ /**
+ * Checks wether the givan host is in the range. Attempts to resolve host
+ * name if required.
+ *
+ * @param host
+ * Host name to check.
+ * @return true If host is in the range, false otherwise.
+ * @see InetRange#contains(String,boolean)
+ */
+ public synchronized boolean contains(final String host) {
+ return contains(host, true);
+ }
+
+ /**
+ * Checks wether the given host is in the range.
+ * <P>
+ * Algorithm: <BR>
+ * <ol>
+ * <li>Look up if the hostname is in the range (in the Hashtable).
+ * <li>Check if it ends with one of the speciefied endings.
+ * <li>Check if it is ip(eg.130.220.35.98). If it is check if it is in the
+ * range.
+ * <li>If attemptResolve is true, host is name, rather than ip, and all
+ * previous attempts failed, try to resolve the hostname, and check wether
+ * the ip associated with the host is in the range.It also repeats all
+ * previos steps with the hostname obtained from InetAddress, but the name
+ * is not allways the full name,it is quite likely to be the same. Well it
+ * was on my machine.
+ * </ol>
+ *
+ * @param host
+ * Host name to check.
+ * @param attemptResolve
+ * Wether to lookup ip address which corresponds to the host,if
+ * required.
+ * @return true If host is in the range, false otherwise.
+ */
+ public synchronized boolean contains(final String host0,
+ final boolean attemptResolve) {
+ if (all.size() == 0) {
+ return false; // Empty range
+ }
+
+ String host = host0.trim();
+ if (host.length() == 0) {
+ return false;
+ }
+
+ if (checkHost(host)) {
+ return true;
+ }
+ if (checkHostEnding(host)) {
+ return true;
+ }
+
+ final long l = host2long(host);
+ if (l >= 0) {
+ return contains(l);
+ }
+
+ if (!attemptResolve) {
+ return false;
+ }
+
+ try {
+ final InetAddress ip = InetAddress.getByName(host);
+ return contains(ip);
+ } catch (final UnknownHostException uhe) {
+
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks wether the given ip is in the range.
+ *
+ * @param ip
+ * Address of the host to check.
+ * @return true If host is in the range, false otherwise.
+ */
+ public synchronized boolean contains(final InetAddress ip) {
+ if (checkHostEnding(ip.getHostName())) {
+ return true;
+ }
+ if (checkHost(ip.getHostName())) {
+ return true;
+ }
+ return contains(ip2long(ip));
+ }
+
+ /**
+ * Get all entries in the range as strings. <BR>
+ * These strings can be used to delete entries from the range with remove
+ * function.
+ *
+ * @return Array of entries as strings.
+ * @see InetRange#remove(String)
+ */
+ public synchronized String[] getAll() {
+ final int size = all.size();
+ Object entry[];
+ final String all_names[] = new String[size];
+
+ for (int i = 0; i < size; ++i) {
+ entry = all.elementAt(i);
+ all_names[i] = (String) entry[0];
+ }
+ return all_names;
+ }
+
+ /**
+ * Removes an entry from this range.<BR>
+ *
+ * @param s
+ * Entry to remove.
+ * @return true if successfull.
+ */
+ public synchronized boolean remove(final String s) {
+ final Enumeration<Object[]> enumx = all.elements();
+ while (enumx.hasMoreElements()) {
+ final Object[] entry = enumx.nextElement();
+ if (s.equals(entry[0])) {
+ all.removeElement(entry);
+ end_names.removeElement(s);
+ host_names.remove(s);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Get string representaion of this Range. */
+ public String toString() {
+ final String all[] = getAll();
+ if (all.length == 0) {
+ return "";
+ }
+
+ String s = all[0];
+ for (int i = 1; i < all.length; ++i) {
+ s += "; " + all[i];
+ }
+ return s;
+ }
+
+ /** Creates a clone of this Object */
+
+ @SuppressWarnings("unchecked")
+ public Object clone() {
+ final InetRange new_range = new InetRange();
+ new_range.all = (Vector<Object[]>) all.clone();
+ new_range.end_names = (Vector<String>) end_names.clone();
+ new_range.host_names = (Hashtable<String, Object[]>) host_names.clone();
+ return new_range;
+ }
+
+ // Private methods
+ // ///////////////
+ /**
+ * Same as previous but used internally, to avoid unnecessary convertion of
+ * IPs, when checking subranges
+ */
+ private synchronized boolean contains(final long ip) {
+ final Enumeration<Object[]> enumx = all.elements();
+ while (enumx.hasMoreElements()) {
+ final Object[] obj = enumx.nextElement();
+ final Long from = obj[2] == null ? null : (Long) obj[2];
+ final Long to = obj[3] == null ? null : (Long) obj[3];
+ if ((from != null) && (from.longValue() <= ip)
+ && (to.longValue() >= ip)) {
+ return true;
+ }
+
+ }
+ return false;
+ }
+
+ private boolean checkHost(final String host) {
+ return host_names.containsKey(host);
+ }
+
+ private boolean checkHostEnding(final String host) {
+ final Enumeration<String> enumx = end_names.elements();
+ while (enumx.hasMoreElements()) {
+ if (host.endsWith(enumx.nextElement())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void resolve(final Object[] entry) {
+ // First check if it's in the form ddd.ddd.ddd.ddd.
+ final long ip = host2long((String) entry[0]);
+ if (ip >= 0) {
+ entry[2] = entry[3] = new Long(ip);
+ } else {
+ final InetRangeResolver res = new InetRangeResolver(entry);
+ res.resolve(useSeparateThread);
+ }
+ }
+
+ private void resolve(final Object[] entry, final String from,
+ final String to) {
+ long f, t;
+ if (((f = host2long(from)) >= 0) && ((t = host2long(to)) >= 0)) {
+ entry[2] = new Long(f);
+ entry[3] = new Long(t);
+ } else {
+ final InetRangeResolver res = new InetRangeResolver(entry, from, to);
+ res.resolve(useSeparateThread);
+ }
+ }
+
+ // Class methods
+ // /////////////
+
+ // Converts ipv4 to long value(unsigned int)
+ // /////////////////////////////////////////
+ static long ip2long(final InetAddress ip) {
+ long l = 0;
+ final byte[] addr = ip.getAddress();
+
+ if (addr.length == 4) { // IPV4
+ for (int i = 0; i < 4; ++i) {
+ l += (((long) addr[i] & 0xFF) << 8 * (3 - i));
+ }
+ } else { // IPV6
+ return 0; // Have no idea how to deal with those
+ }
+ return l;
+ }
+
+ long host2long(final String host) {
+ long ip = 0;
+
+ // check if it's ddd.ddd.ddd.ddd
+ if (!Character.isDigit(host.charAt(0))) {
+ return -1;
+ }
+
+ final int[] addr = ip2intarray(host);
+ if (addr == null) {
+ return -1;
+ }
+
+ for (int i = 0; i < addr.length; ++i) {
+ ip += ((long) (addr[i] >= 0 ? addr[i] : 0)) << 8 * (3 - i);
+ }
+
+ return ip;
+ }
+
+ static int[] ip2intarray(final String host) {
+ final int[] address = { -1, -1, -1, -1 };
+ int i = 0;
+ final StringTokenizer tokens = new StringTokenizer(host, ".");
+ if (tokens.countTokens() > 4) {
+ return null;
+ }
+ while (tokens.hasMoreTokens()) {
+ try {
+ address[i++] = Integer.parseInt(tokens.nextToken()) & 0xFF;
+ } catch (final NumberFormatException nfe) {
+ return null;
+ }
+
+ }
+ return address;
+ }
+
+ /*
+ * //* This was the test main function //**********************************
+ *
+ * public static void main(String args[])throws UnknownHostException{ int i;
+ *
+ * InetRange ir = new InetRange();
+ *
+ *
+ * for(i=0;i<args.length;++i){ System.out.println("Adding:" + args[i]);
+ * ir.add(args[i]); }
+ *
+ * String host; java.io.DataInputStream din = new
+ * java.io.DataInputStream(System.in); try{ host = din.readLine();
+ * while(host!=null){ if(ir.contains(host)){
+ * System.out.println("Range contains ip:"+host); }else{
+ * System.out.println(host+" is not in the range"); } host = din.readLine();
+ * } }catch(java.io.IOException io_ex){ io_ex.printStackTrace(); } }
+ * ******************
+ */
+
+}
+
+class InetRangeResolver implements Runnable {
+
+ Object[] entry;
+
+ String from;
+ String to;
+
+ InetRangeResolver(final Object[] entry) {
+ this.entry = entry;
+ from = null;
+ to = null;
+ }
+
+ InetRangeResolver(final Object[] entry, final String from, final String to) {
+ this.entry = entry;
+ this.from = from;
+ this.to = to;
+ }
+
+ public final void resolve() {
+ resolve(true);
+ }
+
+ public final void resolve(final boolean inSeparateThread) {
+ if (inSeparateThread) {
+ final Thread t = new Thread(this);
+ t.start();
+ } else {
+ run();
+ }
+
+ }
+
+ public void run() {
+ try {
+ if (from == null) {
+ final InetAddress ip = InetAddress.getByName((String) entry[0]);
+ entry[1] = ip;
+ final Long l = new Long(InetRange.ip2long(ip));
+ entry[2] = l;
+ entry[3] = l;
+ } else {
+ final InetAddress f = InetAddress.getByName(from);
+ final InetAddress t = InetAddress.getByName(to);
+ entry[2] = new Long(InetRange.ip2long(f));
+ entry[3] = new Long(InetRange.ip2long(t));
+
+ }
+ } catch (final UnknownHostException uhe) {
+ // System.err.println("Resolve failed for "+from+','+to+','+entry[0]);
+ }
+ }
+
+}
diff --git a/src/com/runjva/sourceforge/jsocks/protocol/ProxyMessage.java b/src/com/runjva/sourceforge/jsocks/protocol/ProxyMessage.java
new file mode 100644
index 0000000..e7721a1
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/ProxyMessage.java
@@ -0,0 +1,118 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+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/com/runjva/sourceforge/jsocks/protocol/ProxyServer.java b/src/com/runjva/sourceforge/jsocks/protocol/ProxyServer.java
new file mode 100644
index 0000000..a672bfa
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/ProxyServer.java
@@ -0,0 +1,669 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.PushbackInputStream;
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NoRouteToHostException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.nio.channels.SocketChannel;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import android.net.VpnService;
+
+import com.runjva.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;
+
+ static int iddleTimeout = 180000; // 3 minutes
+ static int acceptTimeout = 180000; // 3 minutes
+
+ static Logger log = LoggerFactory.getLogger(ProxyServer.class);
+ static SocksProxyBase proxy;
+
+ static VpnService vpnService;
+
+ // Public Constructors
+ // ///////////////////
+
+ /**
+ * Creates a proxy server with given Authentication scheme.
+ *
+ * @param auth
+ * Authentication scheme to be used.
+ */
+ public ProxyServer(final ServerAuthenticator auth) {
+ this.auth = auth;
+ }
+
+ // Other constructors
+ // //////////////////
+
+ ProxyServer(final ServerAuthenticator auth, final Socket s) {
+ this.auth = auth;
+ this.sock = s;
+ this.mode = START_MODE;
+ }
+
+ // Public methods
+ // ///////////////
+
+ /**
+ * 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(final SocksProxyBase p) {
+ proxy = p;
+ // FIXME: Side effect.
+ UDPRelayServer.proxy = proxy;
+ }
+
+ public static void setVpnService (final VpnService v)
+ {
+ vpnService = v;
+ }
+ /**
+ * Get proxy.
+ *
+ * @return Proxy wich is used to handle user requests.
+ */
+ public static SocksProxyBase 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(final 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(final 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(final 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(final int size) {
+ UDPRelayServer.setDatagramSize(size);
+ }
+
+ /**
+ * Start the Proxy server at given port.<br>
+ * This methods blocks.
+ */
+ public void start(final 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(final int port, final int backlog,
+ final InetAddress localIP) {
+ try {
+ ss = new ServerSocket(port, backlog, localIP);
+ final String address = ss.getInetAddress().getHostAddress();
+ final int localPort = ss.getLocalPort();
+ log.info("Starting SOCKS Proxy on: {}:{}", address, localPort);
+
+ while (true) {
+ final Socket s = ss.accept();
+ final String hostName = s.getInetAddress().getHostName();
+ final int port2 = s.getPort();
+ log.info("Accepted from:{}:{}", hostName, port2);
+
+ final ProxyServer ps = new ProxyServer(auth, s);
+ (new Thread(ps)).start();
+ }
+ } catch (final 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 (final IOException ioe) {
+ }
+ }
+
+ // Runnable interface
+ // //////////////////
+ public void run() {
+ switch (mode) {
+ case START_MODE:
+ try {
+ startSession();
+ } catch (final IOException ioe) {
+ handleException(ioe);
+ // ioe.printStackTrace();
+ } finally {
+ abort();
+ if (auth != null) {
+ auth.endSession();
+ }
+ log.info("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 (final IOException ioe) {
+ // log("Accept exception:"+ioe);
+ handleException(ioe);
+ } finally {
+ abort();
+ log.info("Accept thread(remote->client) stopped");
+ }
+ break;
+ case PIPE_MODE:
+ try {
+ pipe(remote_in, out);
+ } catch (final IOException ioe) {
+ } finally {
+ abort();
+ log.info("Support thread(remote->client) stopped");
+ }
+ break;
+ case ABORT_MODE:
+ break;
+ default:
+ log.warn("Unexpected MODE " + mode);
+ }
+ }
+
+ // Private methods
+ // ///////////////
+ private void startSession() throws IOException {
+ sock.setSoTimeout(iddleTimeout);
+
+ try {
+ auth = auth.startSession(sock);
+ } catch (final IOException ioe) {
+ log.warn("Auth throwed exception:", ioe);
+ auth = null;
+ return;
+ }
+
+ if (auth == null) { // Authentication failed
+ log.info("Authentication failed");
+ return;
+ }
+
+ in = auth.getInputStream();
+ out = auth.getOutputStream();
+
+ msg = readMsg(in);
+ handleRequest(msg);
+ }
+
+ private void handleRequest(final ProxyMessage msg) throws IOException {
+ if (!auth.checkRequest(msg)) {
+ throw new SocksException(SocksProxyBase.SOCKS_FAILURE);
+ }
+
+ if (msg.ip == null) {
+ if (msg instanceof Socks5Message) {
+ msg.ip = InetAddress.getByName(msg.host);
+ } else {
+ throw new SocksException(SocksProxyBase.SOCKS_FAILURE);
+ }
+ }
+ log(msg);
+
+ switch (msg.command) {
+ case SocksProxyBase.SOCKS_CMD_CONNECT:
+ onConnect(msg);
+ break;
+ case SocksProxyBase.SOCKS_CMD_BIND:
+ onBind(msg);
+ break;
+ case SocksProxyBase.SOCKS_CMD_UDP_ASSOCIATE:
+ onUDP(msg);
+ break;
+ default:
+ throw new SocksException(SocksProxyBase.SOCKS_CMD_NOT_SUPPORTED);
+ }
+ }
+
+ private void handleException(final 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 = SocksProxyBase.SOCKS_FAILURE;
+
+ if (ioe instanceof SocksException) {
+ error_code = ((SocksException) ioe).errCode;
+ } else if (ioe instanceof NoRouteToHostException) {
+ error_code = SocksProxyBase.SOCKS_HOST_UNREACHABLE;
+ } else if (ioe instanceof ConnectException) {
+ error_code = SocksProxyBase.SOCKS_CONNECTION_REFUSED;
+ } else if (ioe instanceof InterruptedIOException) {
+ error_code = SocksProxyBase.SOCKS_TTL_EXPIRE;
+ }
+
+ if ((error_code > SocksProxyBase.SOCKS_ADDR_NOT_SUPPORTED)
+ || (error_code < 0)) {
+ error_code = SocksProxyBase.SOCKS_FAILURE;
+ }
+
+ sendErrorMessage(error_code);
+ }
+
+ private void onConnect(final ProxyMessage msg) throws IOException {
+ Socket s;
+
+ if (proxy == null) {
+ //s = new Socket(msg.ip, msg.port);
+
+ s= SocketChannel.open().socket();
+ if ((null != s) && (null != vpnService)) {
+ vpnService.protect(s);
+ }
+
+ s.connect(new InetSocketAddress(msg.ip,msg.port));
+
+ } else {
+ s = new SocksSocket(proxy, msg.ip, msg.port);
+ }
+
+ if (vpnService != null)
+ vpnService.protect(s);
+
+ log.info("Connected to " + s.getInetAddress() + ":" + s.getPort());
+
+ ProxyMessage response = null;
+ final InetAddress localAddress = s.getLocalAddress();
+ final int localPort = s.getLocalPort();
+
+ if (msg instanceof Socks5Message) {
+ final int cmd = SocksProxyBase.SOCKS_SUCCESS;
+ response = new Socks5Message(cmd, localAddress, localPort);
+ } else {
+ final int cmd = Socks4Message.REPLY_OK;
+ response = new Socks4Message(cmd, localAddress, localPort);
+
+ }
+ response.write(out);
+ startPipe(s);
+ }
+
+ private void onBind(final 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);
+
+ final InetAddress inetAddress = ss.getInetAddress();
+ final int localPort = ss.getLocalPort();
+ log.info("Trying accept on {}:{}", inetAddress, localPort);
+
+ if (msg.version == 5) {
+ final int cmd = SocksProxyBase.SOCKS_SUCCESS;
+ response = new Socks5Message(cmd, inetAddress, localPort);
+ } else {
+ final int cmd = Socks4Message.REPLY_OK;
+ response = new Socks4Message(cmd, inetAddress, localPort);
+ }
+ 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 (final EOFException e) {
+ log.debug("Connection closed while we were trying to accept", e);
+ return;
+ } catch (final InterruptedIOException e) {
+ log.debug("Interrupted by unsucessful accept thread", e);
+ if (mode != PIPE_MODE) {
+ return;
+ }
+ } finally {
+ // System.out.println("Finnaly!");
+ }
+
+ if (eof < 0) {
+ 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(final ProxyMessage msg) throws IOException {
+ if (msg.ip.getHostAddress().equals("0.0.0.0")) {
+ msg.ip = sock.getInetAddress();
+ }
+ log.info("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(SocksProxyBase.SOCKS_SUCCESS,
+ relayServer.relayIP, relayServer.relayPort);
+
+ response.write(out);
+
+ relayServer.start();
+
+ // Make timeout infinit.
+ sock.setSoTimeout(0);
+ try {
+ while (in.read() >= 0) {
+ /* do nothing */;
+ // FIXME: Consider a slight delay here?
+ }
+ } catch (final EOFException eofe) {
+ }
+ }
+
+ // Private methods
+ // ////////////////
+
+ private void doAccept() throws IOException {
+ Socket s = null;
+ final 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(SocksProxyBase.SOCKS_FAILURE);
+ } else {
+ if (acceptTimeout != 0) { // If timeout is not infinit
+ final long passed = System.currentTimeMillis() - startTime;
+ final int newTimeout = acceptTimeout - (int) passed;
+
+ if (newTimeout <= 0) {
+ throw new InterruptedIOException("newTimeout <= 0");
+ }
+ 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);
+
+ final InetAddress inetAddress = s.getInetAddress();
+ final int port = s.getPort();
+ log.info("Accepted from {}:{}", s.getInetAddress(), port);
+
+ ProxyMessage response;
+
+ if (msg.version == 5) {
+ final int cmd = SocksProxyBase.SOCKS_SUCCESS;
+ response = new Socks5Message(cmd, inetAddress, port);
+ } else {
+ final int cmd = Socks4Message.REPLY_OK;
+ response = new Socks4Message(cmd, inetAddress, port);
+ }
+ response.write(out);
+ }
+
+ private ProxyMessage readMsg(final InputStream in) throws IOException {
+ PushbackInputStream push_in;
+ if (in instanceof PushbackInputStream) {
+ push_in = (PushbackInputStream) in;
+ } else {
+ push_in = new PushbackInputStream(in);
+ }
+
+ final 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(SocksProxyBase.SOCKS_FAILURE);
+ }
+ return msg;
+ }
+
+ private void startPipe(final 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 (final IOException ioe) {
+ }
+ }
+
+ private void sendErrorMessage(final 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 (final IOException ioe) {
+ }
+ }
+
+ private synchronized void abort() {
+ if (mode == ABORT_MODE) {
+ return;
+ }
+ mode = ABORT_MODE;
+ try {
+ log.info("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 (final IOException ioe) {
+ }
+ }
+
+ static final void log(final ProxyMessage msg) {
+ log.debug("Request version: {}, Command: ", msg.version,
+ command2String(msg.command));
+
+ final String user = msg.version == 4 ? ", User:" + msg.user : "";
+ log.debug("IP:" + msg.ip + ", Port:" + msg.port + user);
+ }
+
+ private void pipe(final InputStream in, final OutputStream out)
+ throws IOException {
+ lastReadTime = System.currentTimeMillis();
+ final 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 (final InterruptedIOException iioe) {
+ if (iddleTimeout == 0) {
+ return;// Other thread interrupted us.
+ }
+ final long timeSinceRead = System.currentTimeMillis()
+ - lastReadTime;
+
+ if (timeSinceRead >= iddleTimeout - 1000) {
+ 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/com/runjva/sourceforge/jsocks/protocol/Socks4Message.java b/src/com/runjva/sourceforge/jsocks/protocol/Socks4Message.java
new file mode 100644
index 0000000..484ad96
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/Socks4Message.java
@@ -0,0 +1,167 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * SOCKS4 Reply/Request message.
+ */
+
+class Socks4Message extends ProxyMessage {
+
+ private byte[] msgBytes;
+ private int msgLength;
+
+ /**
+ * Server failed reply, cmd command for failed request
+ */
+ public Socks4Message(final 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(final int cmd, final InetAddress ip, final int port) {
+ this(0, cmd, ip, port, null);
+ }
+
+ /**
+ * Client request
+ */
+ public Socks4Message(final int cmd, final InetAddress ip, final int port,
+ final String user) {
+ this(SOCKS_VERSION, cmd, ip, port, user);
+ }
+
+ /**
+ * Most general constructor
+ */
+ public Socks4Message(final int version, final int cmd,
+ final InetAddress ip, final int port, final 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) {
+ final 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(final InputStream in, final boolean clientMode)
+ throws IOException {
+ msgBytes = null;
+ read(in, clientMode);
+ }
+
+ public void read(final InputStream in) throws IOException {
+ read(in, true);
+ }
+
+ public void read(final InputStream in, final boolean clientMode)
+ throws IOException {
+ final DataInputStream d_in = new DataInputStream(in);
+ version = d_in.readUnsignedByte();
+ command = d_in.readUnsignedByte();
+ if (clientMode && (command != REPLY_OK)) {
+ String errMsg;
+ // FIXME: Range should be replaced with cases.
+ 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();
+ final byte[] addr = new byte[4];
+ d_in.readFully(addr);
+ ip = bytes2IP(addr);
+ host = ip.getHostName();
+ if (!clientMode) {
+ int b = in.read();
+ // FIXME: Hope there are no idiots with user name bigger than this
+ final 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(final OutputStream out) throws IOException {
+ if (msgBytes == null) {
+ final Socks4Message msg;
+ msg = new Socks4Message(version, command, ip, port, user);
+ msgBytes = msg.msgBytes;
+ msgLength = msg.msgLength;
+ }
+ out.write(msgBytes);
+ }
+
+ // Class methods
+ static InetAddress bytes2IP(final byte[] addr) {
+ final String s = bytes2IPV4(addr, 0);
+ try {
+ return InetAddress.getByName(s);
+ } catch (final 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/com/runjva/sourceforge/jsocks/protocol/Socks4Proxy.java b/src/com/runjva/sourceforge/jsocks/protocol/Socks4Proxy.java
new file mode 100644
index 0000000..5850b5e
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/Socks4Proxy.java
@@ -0,0 +1,144 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+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 SocksProxyBase 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(SocksProxyBase p, String proxyHost, int proxyPort,
+ String user) throws UnknownHostException {
+ super(p, proxyHost, proxyPort);
+ this.user = new String(user);
+ version = 4;
+ }
+
+ /**
+ * Creates the SOCKS4 proxy
+ *
+ * @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 {
+ this(null, proxyHost, proxyPort, user);
+ }
+
+ /**
+ * 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(SocksProxyBase 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() {
+ final Socks4Proxy newProxy = new Socks4Proxy(proxyIP, proxyPort, user);
+ newProxy.directHosts = (InetRange) directHosts.clone();
+ newProxy.chainProxy = chainProxy;
+ return newProxy;
+ }
+
+ // Public Static(Class) Methods
+ // ==============================
+
+ // Protected Methods
+ // =================
+
+ protected SocksProxyBase copy() {
+ final Socks4Proxy copy = new Socks4Proxy(proxyIP, proxyPort, user);
+ copy.directHosts = this.directHosts;
+ 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/com/runjva/sourceforge/jsocks/protocol/Socks5DatagramSocket.java b/src/com/runjva/sourceforge/jsocks/protocol/Socks5DatagramSocket.java
new file mode 100644
index 0000000..6dcaf44
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/Socks5DatagramSocket.java
@@ -0,0 +1,485 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 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;
+
+ private Logger log = LoggerFactory.getLogger(Socks5DatagramSocket.class);
+
+ /**
+ * 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(SocksProxyBase.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(SocksProxyBase.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(SocksProxyBase.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 datagram socket to given address/port
+ * fails. See java.net.DatagramSocket for more details.
+ */
+ public Socks5DatagramSocket(SocksProxyBase p, int port, InetAddress ip)
+ throws SocksException, IOException {
+
+ super(port, ip);
+
+ if (p == null) {
+ throw new SocksException(SocksProxyBase.SOCKS_NO_PROXY);
+ }
+
+ if (!(p instanceof Socks5Proxy)) {
+ final String s = "Datagram Socket needs Proxy version 5";
+ throw new SocksException(-1, s);
+ }
+
+ if (p.chainProxy != null) {
+ final String s = "Datagram Sockets do not support proxy chaining.";
+ throw new SocksException(SocksProxyBase.SOCKS_JUST_ERROR, s);
+ }
+
+ proxy = (Socks5Proxy) p.copy();
+
+ final ProxyMessage msg = proxy.udpAssociate(super.getLocalAddress(),
+ super.getLocalPort());
+
+ relayIP = msg.ip;
+ if (relayIP.getHostAddress().equals("0.0.0.0")) {
+ // FIXME: What happens here?
+ relayIP = proxy.proxyIP;
+ }
+ relayPort = msg.port;
+
+ encapsulation = proxy.udp_encapsulation;
+
+ log.debug("Datagram Socket:{}:{}", getLocalAddress(), getLocalPort());
+ log.debug("Socks5Datagram: {}:{}", relayIP, relayPort);
+ }
+
+ /**
+ * 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 && proxy.isDirect(dp.getAddress())) {
+ super.send(dp);
+ log.debug("Sending datagram packet directly:");
+ return;
+ }
+
+ final byte[] head = formHeader(dp.getAddress(), dp.getPort());
+ byte[] buf = new byte[head.length + dp.getLength()];
+ final 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 {
+ if (proxy.isDirect(host)) {
+ dp.setAddress(InetAddress.getByName(host));
+ super.send(dp);
+ return;
+ }
+
+ if ((proxy).resolveAddrLocally) {
+ dp.setAddress(InetAddress.getByName(host));
+ }
+
+ final byte[] head = formHeader(host, dp.getPort());
+ byte[] buf = new byte[head.length + dp.getLength()];
+ final 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));
+ }
+
+ /**
+ * 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
+ final int init_length = dp.getLength();
+ final int initTimeout = getSoTimeout();
+ final 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) {
+ final long passed = System.currentTimeMillis() - startTime;
+ final int newTimeout = initTimeout - (int) passed;
+
+ 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);
+ }
+
+ // FIXME: What is this?
+ final int offset = 0; // Java 1.1
+ // int offset = dp.getOffset(); //Java 1.2
+
+ final ByteArrayInputStream bIn = new ByteArrayInputStream(data, offset,
+ dp.getLength());
+
+ final ProxyMessage msg = new Socks5Message(bIn);
+ dp.setPort(msg.port);
+ dp.setAddress(msg.getInetAddress());
+
+ // what wasn't read by the Message is the data
+ final 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);
+
+ final int eof = proxy.in.read();
+ if (eof < 0) {
+ return false; // EOF encountered.
+ } else {
+ log.warn("This really should not happen");
+ return true; // This really should not happen
+ }
+
+ } catch (final InterruptedIOException iioe) {
+ return true; // read timed out.
+ } catch (final IOException ioe) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ // PRIVATE METHODS
+ // ////////////////
+
+ private byte[] formHeader(InetAddress ip, int port) {
+ final Socks5Message request = new Socks5Message(0, ip, port);
+ request.data[0] = 0;
+ return request.data;
+ }
+
+ private byte[] formHeader(String host, int port) {
+ final 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/com/runjva/sourceforge/jsocks/protocol/Socks5Message.java b/src/com/runjva/sourceforge/jsocks/protocol/Socks5Message.java
new file mode 100644
index 0000000..cb62938
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/Socks5Message.java
@@ -0,0 +1,330 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * SOCKS5 request/response message.
+ */
+
+class Socks5Message extends ProxyMessage {
+ /** Address type of given message */
+ public int addrType;
+
+ byte[] data;
+
+ private Logger log = LoggerFactory.getLogger(Socks5Message.class);
+
+ /**
+ * 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);
+
+ if (ip == null) {
+ this.host = "0.0.0.0";
+ } else {
+ this.host = 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();
+ }
+
+ if (addr.length == 4) {
+ addrType = SOCKS_ATYP_IPV4;
+ } else {
+ addrType = 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;
+
+ log.debug("Doing ATYP_DOMAINNAME");
+
+ addrType = SOCKS_ATYP_DOMAINNAME;
+ final 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;
+
+ final DataInputStream di = new DataInputStream(in);
+
+ version = di.readUnsignedByte();
+ command = di.readUnsignedByte();
+
+ if (clientMode && (command != 0)) {
+ throw new SocksException(command);
+ }
+
+ 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:
+ log.debug("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(SocksProxyBase.SOCKS_JUST_ERROR));
+ }
+
+ port = di.readUnsignedShort();
+
+ if ((addrType != SOCKS_ATYP_DOMAINNAME) && doResolveIP) {
+ try {
+ ip = InetAddress.getByName(host);
+ } catch (final 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 (final UnknownHostException uh_ex) {
+ throw new SocksException(
+ SocksProxyBase.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() {
+ // FIXME: Single line version, please.
+ final 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) {
+ final 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/com/runjva/sourceforge/jsocks/protocol/Socks5Proxy.java b/src/com/runjva/sourceforge/jsocks/protocol/Socks5Proxy.java
new file mode 100644
index 0000000..7400aa6
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/Socks5Proxy.java
@@ -0,0 +1,295 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+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 SocksProxyBase 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 p
+ * Proxy to use to connect to this proxy, allows proxy chaining.
+ * @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(SocksProxyBase p, String proxyHost, int proxyPort)
+ throws UnknownHostException {
+
+ super(p, proxyHost, proxyPort);
+ version = 5;
+ setAuthenticationMethod(0, new AuthenticationNone());
+ }
+
+ /**
+ * 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 {
+ this(null, proxyHost, proxyPort);
+ }
+
+ /**
+ * Creates SOCKS5 proxy.
+ *
+ * @param p
+ * Proxy to use to connect to this proxy, allows proxy chaining.
+ * @param proxyIP
+ * Host on which a Proxy server runs.
+ * @param proxyPort
+ * Port on which a Proxy server listens for connections.
+ */
+ public Socks5Proxy(SocksProxyBase p, InetAddress proxyIP, int proxyPort) {
+ super(p, proxyIP, 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) {
+ this(null, proxyIP, proxyPort);
+ }
+
+ // 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) {
+ final 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) {
+ final Object method = authMethods.get(new Integer(methodId));
+ if (method == null) {
+ return null;
+ }
+ return (Authentication) method;
+ }
+
+ /**
+ * Creates a clone of this Proxy. clone() returns an
+ */
+ @SuppressWarnings("unchecked")
+ public Object clone() {
+ final Socks5Proxy newProxy = new Socks5Proxy(proxyIP, proxyPort);
+
+ final Object o = this.authMethods.clone();
+ newProxy.authMethods = (Hashtable<Integer, Authentication>) o;
+
+ newProxy.directHosts = (InetRange) directHosts.clone();
+ newProxy.resolveAddrLocally = resolveAddrLocally;
+ newProxy.chainProxy = chainProxy;
+ return newProxy;
+ }
+
+ // Public Static(Class) Methods
+ // ==============================
+
+ // Protected Methods
+ // =================
+
+ protected SocksProxyBase copy() {
+ final Socks5Proxy copy = new Socks5Proxy(proxyIP, proxyPort);
+
+ copy.authMethods = this.authMethods; // same Hash, no copy
+ copy.directHosts = this.directHosts;
+ copy.chainProxy = this.chainProxy;
+ copy.resolveAddrLocally = this.resolveAddrLocally;
+ return copy;
+ }
+
+ /**
+ *
+ *
+ */
+ protected void startSession() throws SocksException {
+ super.startSession();
+ Authentication auth;
+ final Socket ps = proxySocket; // The name is too long
+
+ try {
+
+ final byte nMethods = (byte) authMethods.size(); // Number of
+ // methods
+
+ final byte[] buf = new byte[2 + nMethods]; // 2 is for VER,NMETHODS
+ buf[0] = (byte) version;
+ buf[1] = nMethods; // Number of methods
+ int i = 2;
+
+ final Enumeration<Integer> ids = authMethods.keys();
+ while (ids.hasMoreElements()) {
+ buf[i++] = (byte) ids.nextElement().intValue();
+ }
+
+ out.write(buf);
+ out.flush();
+
+ final int versionNumber = in.read();
+ selectedMethod = in.read();
+
+ if ((versionNumber < 0) || (selectedMethod < 0)) {
+ // EOF condition was reached
+ endSession();
+ final String s = "Connection to proxy lost.";
+ throw new SocksException(SOCKS_PROXY_IO_ERROR, s);
+ }
+
+ 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
+ final String s = "Specified Authentication not found!";
+ throw new SocksException(SOCKS_JUST_ERROR, s);
+ }
+
+ final Object[] in_out;
+ 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 (final SocksException s_ex) {
+ throw s_ex;
+ } catch (final UnknownHostException uh_ex) {
+ throw new SocksException(SOCKS_PROXY_NO_CONNECT, uh_ex);
+ } catch (final SocketException so_ex) {
+ throw new SocksException(SOCKS_PROXY_NO_CONNECT, so_ex);
+ } catch (final IOException 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/com/runjva/sourceforge/jsocks/protocol/SocksException.java b/src/com/runjva/sourceforge/jsocks/protocol/SocksException.java
new file mode 100644
index 0000000..a651597
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/SocksException.java
@@ -0,0 +1,111 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+/**
+ * Exception thrown by various socks classes to indicate errors with protocol or
+ * unsuccessfull server responses.
+ */
+public class SocksException extends java.io.IOException {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct a SocksException with given errorcode.
+ * <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;
+ lookupErrorString(errCode);
+ }
+
+ private void lookupErrorString(int errCode) {
+ if ((errCode >> 16) == 0) {
+ if (errCode <= serverReplyMessage.length) {
+ errString = serverReplyMessage[errCode];
+ } else {
+ errString = UNASSIGNED_ERROR_MESSAGE;
+ }
+ } else {
+ // Local error
+ errCode = (errCode >> 16) - 1;
+ if (errCode <= localErrorMessage.length) {
+ errString = localErrorMessage[errCode];
+ } else {
+ errString = UNASSIGNED_ERROR_MESSAGE;
+ }
+ }
+ }
+
+ /**
+ * Construct a SocksException with given error code, and a Throwable cause
+ *
+ * @param errCode
+ * @param t
+ * Nested exception for debugging purposes.
+ */
+ public SocksException(int errCode, Throwable t) {
+ super(t); // Java 1.6+
+ this.errCode = errCode;
+ lookupErrorString(errCode);
+ }
+
+ /**
+ * 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;
+ }
+
+ public SocksException(int errCode, String string, Throwable t) {
+ super(string, t); // Java 1.6+
+ this.errCode = errCode;
+ this.errString = string;
+ }
+
+ /**
+ * 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/com/runjva/sourceforge/jsocks/protocol/SocksProxyBase.java b/src/com/runjva/sourceforge/jsocks/protocol/SocksProxyBase.java
new file mode 100644
index 0000000..ebf4fdb
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/SocksProxyBase.java
@@ -0,0 +1,543 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+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 SocksProxyBase {
+
+ // 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 SocksProxyBase chainProxy = null;
+
+ // Protected static/class variables
+ protected static SocksProxyBase defaultProxy = null;
+
+ // Constructors
+ // ====================
+ SocksProxyBase(SocksProxyBase chainProxy, String proxyHost, int proxyPort)
+ throws UnknownHostException {
+ this.chainProxy = chainProxy;
+ this.proxyHost = proxyHost;
+
+ if (chainProxy == null) {
+ this.proxyIP = InetAddress.getByName(proxyHost);
+ }
+
+ this.proxyPort = proxyPort;
+ }
+
+ SocksProxyBase(String proxyHost, int proxyPort) throws UnknownHostException {
+ this(null, proxyHost, proxyPort);
+ }
+
+ SocksProxyBase(SocksProxyBase chainProxy, InetAddress proxyIP, int proxyPort) {
+ this.chainProxy = chainProxy;
+ this.proxyIP = proxyIP;
+ this.proxyPort = proxyPort;
+ }
+
+ SocksProxyBase(InetAddress proxyIP, int proxyPort) {
+ this(null, proxyIP, proxyPort);
+ }
+
+ SocksProxyBase(SocksProxyBase p) {
+ this.proxyIP = p.proxyIP;
+ this.proxyPort = p.proxyPort;
+ this.version = p.version;
+ this.directHosts = p.directHosts;
+ }
+
+ // 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;
+ }
+
+ /**
+ * Adds given ip to the list of direct addresses. This machine will be
+ * accessed without using proxy.
+ */
+ public void addDirect(InetAddress ip) {
+ directHosts.add(ip);
+ }
+
+ /**
+ * Adds host to the list of direct addresses. This machine will be accessed
+ * without using proxy.
+ */
+ public boolean addDirect(String host) {
+ return directHosts.add(host);
+ }
+
+ /**
+ * Adds given range of addresses to the lsit of direct addresses, machines
+ * within this range will be accessed without using proxy.
+ */
+ public void addDirect(InetAddress from, InetAddress to) {
+ directHosts.add(from, to);
+ }
+
+ /**
+ * Sets given InetRange as the list of direct address, previous list will be
+ * discarded, any changes done previously with addDirect(Inetaddress) will
+ * be lost. The machines in this range will be accessed without using proxy.
+ *
+ * @param ir
+ * InetRange which should be used to look up direct addresses.
+ * @see InetRange
+ */
+ public void setDirect(InetRange ir) {
+ directHosts = ir;
+ }
+
+ /**
+ * Get the list of direct hosts.
+ *
+ * @return Current range of direct address as InetRange object.
+ * @see InetRange
+ */
+ public InetRange getDirect() {
+ return directHosts;
+ }
+
+ /**
+ * Check wether the given host is on the list of direct address.
+ *
+ * @param host
+ * Host name to check.
+ * @return true if the given host is specified as the direct addresses.
+ */
+ public boolean isDirect(String host) {
+ return directHosts.contains(host);
+ }
+
+ /**
+ * Check wether the given host is on the list of direct addresses.
+ *
+ * @param host
+ * Host address to check.
+ * @return true if the given host is specified as the direct address.
+ */
+ public boolean isDirect(InetAddress host) {
+ return directHosts.contains(host);
+ }
+
+ /**
+ * Set the proxy which should be used to connect to given proxy.
+ *
+ * @param chainProxy
+ * Proxy to use to connect to this proxy.
+ */
+ public void setChainProxy(SocksProxyBase chainProxy) {
+ this.chainProxy = chainProxy;
+ }
+
+ /**
+ * Get proxy which is used to connect to this proxy.
+ *
+ * @return Proxy which is used to connect to this proxy, or null if proxy is
+ * to be contacted directly.
+ */
+ public SocksProxyBase getChainProxy() {
+ return chainProxy;
+ }
+
+ /**
+ * 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(SocksProxyBase p) {
+ defaultProxy = p;
+ }
+
+ /**
+ * Get current default proxy.
+ *
+ * @return Current default proxy, or null if none is set.
+ */
+ public static SocksProxyBase 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 SocksProxyBase parseProxy(String proxy_entry) {
+
+ String proxy_host;
+ int proxy_port = 1080;
+ String proxy_user = null;
+ String proxy_password = null;
+ SocksProxyBase proxy;
+
+ final 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 (final 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);
+ final UserPasswordAuthentication upa = new UserPasswordAuthentication(
+ proxy_user, proxy_password);
+
+ ((Socks5Proxy) proxy).setAuthenticationMethod(
+ UserPasswordAuthentication.METHOD_ID, upa);
+ }
+ } catch (final UnknownHostException uhe) {
+ return null;
+ }
+
+ return proxy;
+ }
+
+ // Protected Methods
+ // =================
+
+ protected void startSession() throws SocksException {
+ try {
+ if (chainProxy == null) {
+ proxySocket = new Socket(proxyIP, proxyPort);
+ } else if (proxyIP != null) {
+ proxySocket = new SocksSocket(chainProxy, proxyIP, proxyPort);
+ } else {
+ proxySocket = new SocksSocket(chainProxy, proxyHost, proxyPort);
+ }
+
+ in = proxySocket.getInputStream();
+ out = proxySocket.getOutputStream();
+ } catch (final SocksException se) {
+ throw se;
+ } catch (final IOException io_ex) {
+ throw new SocksException(SOCKS_PROXY_IO_ERROR, "" + io_ex);
+ }
+ }
+
+ /**
+ * Create a copy of this proxy for use by individual threads.
+ *
+ * @return proxy
+ */
+ protected abstract SocksProxyBase 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();
+ final ProxyMessage request = formMessage(SOCKS_CMD_CONNECT, ip,
+ port);
+ return exchange(request);
+ } catch (final SocksException se) {
+ endSession();
+ throw se;
+ }
+ }
+
+ protected ProxyMessage connect(String host, int port)
+ throws UnknownHostException, SocksException {
+ try {
+ startSession();
+ final ProxyMessage request = formMessage(SOCKS_CMD_CONNECT, host,
+ port);
+ return exchange(request);
+ } catch (final SocksException se) {
+ endSession();
+ throw se;
+ }
+ }
+
+ protected ProxyMessage bind(InetAddress ip, int port) throws SocksException {
+ try {
+ startSession();
+ final ProxyMessage request = formMessage(SOCKS_CMD_BIND, ip, port);
+ return exchange(request);
+ } catch (final SocksException se) {
+ endSession();
+ throw se;
+ }
+ }
+
+ protected ProxyMessage bind(String host, int port)
+ throws UnknownHostException, SocksException {
+ try {
+ startSession();
+ final ProxyMessage request = formMessage(SOCKS_CMD_BIND, host, port);
+ return exchange(request);
+ } catch (final SocksException se) {
+ endSession();
+ throw se;
+ }
+ }
+
+ protected ProxyMessage accept() throws IOException, SocksException {
+ ProxyMessage msg;
+ try {
+ msg = formMessage(in);
+ } catch (final InterruptedIOException iioe) {
+ throw iioe;
+ } catch (final 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();
+ final ProxyMessage request = formMessage(SOCKS_CMD_UDP_ASSOCIATE,
+ ip, port);
+ if (request != null) {
+ return exchange(request);
+ }
+ } catch (final 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();
+ final ProxyMessage request = formMessage(SOCKS_CMD_UDP_ASSOCIATE,
+ host, port);
+ if (request != null) {
+ return exchange(request);
+ }
+ } catch (final 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 (final 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 (final SocksException s_ex) {
+ throw s_ex;
+ } catch (final 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/com/runjva/sourceforge/jsocks/protocol/SocksServerSocket.java b/src/com/runjva/sourceforge/jsocks/protocol/SocksServerSocket.java
new file mode 100644
index 0000000..7153a13
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/SocksServerSocket.java
@@ -0,0 +1,238 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+/**
+ * SocksServerSocket allows to accept connections from one particular host
+ * through the SOCKS4 or SOCKS5 proxy.
+ */
+public class SocksServerSocket extends ServerSocket {
+ // Data members
+ protected SocksProxyBase 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 default 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 {
+ this(SocksProxyBase.defaultProxy, host, port);
+ }
+
+ /**
+ *Creates ServerSocket capable of accepting one connection through the
+ * firewall, uses given proxy.
+ *
+ * @param p
+ * Proxy object to use.
+ *@param host
+ * Host from which the connection should be recieved.
+ *@param port
+ * Port number of the primary connection.
+ */
+ public SocksServerSocket(SocksProxyBase p, String host, int port)
+ throws SocksException, UnknownHostException, IOException {
+
+ super(0);
+ if (p == null) {
+ throw new SocksException(SocksProxyBase.SOCKS_NO_PROXY);
+ }
+ // proxy=p;
+ proxy = p.copy();
+ if (proxy.isDirect(host)) {
+ remoteAddr = InetAddress.getByName(host);
+ proxy = null;
+ doDirect();
+ } else {
+ processReply(proxy.bind(host, port));
+ }
+ }
+
+ /**
+ * 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(SocksProxyBase.defaultProxy, ip, port);
+ }
+
+ /**
+ *Creates ServerSocket capable of accepting one connection through the
+ * firewall, uses given proxy.
+ *
+ * @param p
+ * Proxy object to use.
+ *@param ip
+ * Host from which the connection should be recieved.
+ *@param port
+ * Port number of the primary connection.
+ */
+ public SocksServerSocket(SocksProxyBase p, InetAddress ip, int port)
+ throws SocksException, IOException {
+ super(0);
+
+ if (p == null) {
+ throw new SocksException(SocksProxyBase.SOCKS_NO_PROXY);
+ }
+ this.proxy = p.copy();
+
+ if (proxy.isDirect(ip)) {
+ remoteAddr = ip;
+ doDirect();
+ } else {
+ processReply(proxy.bind(ip, port));
+ }
+ }
+
+ /**
+ * Accepts the incoming connection.
+ */
+ public Socket accept() throws IOException {
+ Socket s;
+
+ if (!doing_direct) {
+ if (proxy == null) {
+ return null;
+ }
+
+ final 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);
+ if (ProxyServer.vpnService != null)
+ ProxyServer.vpnService.protect(proxy.proxySocket);
+
+ } 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 (final 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/com/runjva/sourceforge/jsocks/protocol/SocksSocket.java b/src/com/runjva/sourceforge/jsocks/protocol/SocksSocket.java
new file mode 100644
index 0000000..674d24e
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/SocksSocket.java
@@ -0,0 +1,389 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 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 SocksProxyBase proxy;
+ protected String localHost, remoteHost;
+ protected InetAddress localIP, remoteIP;
+ protected int localPort, remotePort;
+
+ private Socket directSock = null;
+ private Logger log = LoggerFactory.getLogger(SocksSocket.class);
+
+ /**
+ * 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(SocksProxyBase,String,int)
+ * @see Socks5Proxy#resolveAddrLocally
+ */
+ public SocksSocket(String host, int port) throws SocksException,
+ UnknownHostException {
+ this(SocksProxyBase.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(SocksProxyBase p, String host, int port)
+ throws SocksException, UnknownHostException {
+
+ if (p == null) {
+ throw new SocksException(SocksProxyBase.SOCKS_NO_PROXY);
+ }
+ // proxy=p;
+ proxy = p.copy();
+ remoteHost = host;
+ remotePort = port;
+ if (proxy.isDirect(host)) {
+ remoteIP = InetAddress.getByName(host);
+ doDirect();
+ } else {
+ processReply(proxy.connect(host, port));
+ }
+ }
+
+ /**
+ * Tryies to connect to given ip and port using default proxy. If no default
+ * proxy speciefied it throws SocksException with error code SOCKS_NO_PROXY.
+ *
+ * @param ip
+ * Machine to connect to.
+ * @param port
+ * Port to which to connect.
+ * @see SocksSocket#SocksSocket(SocksProxyBase,String,int)
+ */
+ public SocksSocket(InetAddress ip, int port) throws SocksException {
+ this(SocksProxyBase.defaultProxy, ip, port);
+ }
+
+ /**
+ * 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(SocksProxyBase p, InetAddress ip, int port)
+ throws SocksException {
+ if (p == null) {
+ throw new SocksException(SocksProxyBase.SOCKS_NO_PROXY);
+ }
+ this.proxy = p.copy();
+ this.remoteIP = ip;
+ this.remotePort = port;
+ this.remoteHost = ip.getHostName();
+ if (proxy.isDirect(remoteIP)) {
+ doDirect();
+ } else {
+ processReply(proxy.connect(ip, port));
+ }
+ }
+
+ /**
+ * These 2 constructors are used by the SocksServerSocket. This socket
+ * simply overrides remoteHost, remotePort
+ */
+ protected SocksSocket(String host, int port, SocksProxyBase 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, SocksProxyBase 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 (final 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 (final 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;
+ }
+ StringBuffer sb = new StringBuffer();
+ sb.append("Proxy:");
+ sb.append(proxy);
+ sb.append(";");
+ sb.append("addr:");
+ sb.append(remoteHost);
+ sb.append(",port:");
+ sb.append(remotePort);
+ sb.append(",localport:");
+ sb.append(localPort);
+ return sb.toString();
+
+ }
+
+ // 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 {
+ log.debug("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 (final IOException io_ex) {
+ final int errCode = SocksProxyBase.SOCKS_DIRECT_FAILED;
+ throw new SocksException(errCode, "Direct connect failed:", io_ex);
+ }
+ }
+
+}
diff --git a/src/com/runjva/sourceforge/jsocks/protocol/UDPEncapsulation.java b/src/com/runjva/sourceforge/jsocks/protocol/UDPEncapsulation.java
new file mode 100644
index 0000000..eaa4614
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/UDPEncapsulation.java
@@ -0,0 +1,33 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+/**
+ * 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/com/runjva/sourceforge/jsocks/protocol/UDPRelayServer.java b/src/com/runjva/sourceforge/jsocks/protocol/UDPRelayServer.java
new file mode 100644
index 0000000..08bc723
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/UDPRelayServer.java
@@ -0,0 +1,231 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.runjva.sourceforge.jsocks.server.ServerAuthenticator;
+
+/**
+ * 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 Logger log = LoggerFactory.getLogger(UDPRelayServer.class);
+ static SocksProxyBase 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.info("Starting UDP relay server on {}:{}", relayIP, relayPort);
+ log.info("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 (final IOException ioe) {
+ } finally {
+ abort();
+ log.info("UDP Pipe thread " + Thread.currentThread().getName()
+ + " stopped.");
+ }
+
+ }
+
+ // Private methods
+ // ///////////////
+ private synchronized void abort() {
+ if (pipe_thread1 == null) {
+ return;
+ }
+
+ log.info("Aborting UDP Relay Server");
+
+ remote_sock.close();
+ client_sock.close();
+
+ if (controlConnection != null) {
+ try {
+ controlConnection.close();
+ } catch (final IOException ioe) {
+ }
+ }
+
+ if (master_thread != null) {
+ master_thread.interrupt();
+ }
+
+ pipe_thread1.interrupt();
+ pipe_thread2.interrupt();
+
+ pipe_thread1 = null;
+ }
+
+ private void pipe(DatagramSocket from, DatagramSocket to, boolean out)
+ throws IOException {
+ final byte[] data = new byte[datagramSize];
+ final 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 (final UnknownHostException uhe) {
+ log.info("Dropping datagram for unknown host");
+ } catch (final 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.
+ final long timeSinceRead = System.currentTimeMillis()
+ - lastReadTime;
+ if (timeSinceRead >= iddleTimeout - 100) {
+ return;
+ }
+ }
+ dp.setLength(data.length);
+ }
+ }
+}
diff --git a/src/com/runjva/sourceforge/jsocks/protocol/UserPasswordAuthentication.java b/src/com/runjva/sourceforge/jsocks/protocol/UserPasswordAuthentication.java
new file mode 100644
index 0000000..2d5e600
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/protocol/UserPasswordAuthentication.java
@@ -0,0 +1,91 @@
+package com.runjva.sourceforge.jsocks.protocol;
+
+/**
+ * SOCKS5 User Password authentication scheme.
+ */
+public class UserPasswordAuthentication implements Authentication {
+
+ /** SOCKS ID for User/Password authentication method */
+ public final static int METHOD_ID = 2;
+
+ String userName, password;
+ byte[] request;
+
+ /**
+ * Create an instance of UserPasswordAuthentication.
+ *
+ * @param userName
+ * User Name to send to SOCKS server.
+ * @param password
+ * Password to send to SOCKS server.
+ */
+ public UserPasswordAuthentication(String userName, String password) {
+ this.userName = userName;
+ this.password = password;
+ formRequest();
+ }
+
+ /**
+ * Get the user name.
+ *
+ * @return User name.
+ */
+ public String getUser() {
+ return userName;
+ }
+
+ /**
+ * Get password
+ *
+ * @return Password
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Does User/Password authentication as defined in rfc1929.
+ *
+ * @return An array containnig in, out streams, or null if authentication
+ * fails.
+ */
+ public Object[] doSocksAuthentication(int methodId,
+ java.net.Socket proxySocket) throws java.io.IOException {
+
+ if (methodId != METHOD_ID) {
+ return null;
+ }
+
+ final java.io.InputStream in = proxySocket.getInputStream();
+ final java.io.OutputStream out = proxySocket.getOutputStream();
+
+ out.write(request);
+ final int version = in.read();
+ if (version < 0) {
+ return null; // Server closed connection
+ }
+ final int status = in.read();
+ if (status != 0) {
+ return null; // Server closed connection, or auth failed.
+ }
+
+ return new Object[] { in, out };
+ }
+
+ // Private methods
+ // ////////////////
+
+ /** Convert UserName password in to binary form, ready to be send to server */
+ private void formRequest() {
+ final byte[] user_bytes = userName.getBytes();
+ final byte[] password_bytes = password.getBytes();
+
+ request = new byte[3 + user_bytes.length + password_bytes.length];
+ request[0] = (byte) 1;
+ request[1] = (byte) user_bytes.length;
+ System.arraycopy(user_bytes, 0, request, 2, user_bytes.length);
+ request[2 + user_bytes.length] = (byte) password_bytes.length;
+ System.arraycopy(password_bytes, 0, request, 3 + user_bytes.length,
+ password_bytes.length);
+ }
+}
diff --git a/src/com/runjva/sourceforge/jsocks/server/Ident.java b/src/com/runjva/sourceforge/jsocks/server/Ident.java
new file mode 100644
index 0000000..7193f79
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/server/Ident.java
@@ -0,0 +1,176 @@
+package com.runjva.sourceforge.jsocks.server;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.util.StringTokenizer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class Ident provides means to obtain user name of the owner of the socket on
+ * remote machine, providing remote machine runs identd daemon.
+ * <p>
+ * To use it: <tt><pre>
+ Socket s = ss.accept();
+ Ident id = new Ident(s);
+ if(id.successful) goUseUser(id.userName);
+ else handleIdentError(id.errorCode,id.errorMessage)
+ </pre></tt>
+ */
+public class Ident {
+
+ Logger log = LoggerFactory.getLogger(Ident.class);
+
+ /** Error Message can be null. */
+ public String errorMessage;
+
+ /** Host type as returned by daemon, can be null, if error happened */
+ public String hostType;
+
+ /** User name as returned by the identd daemon, or null, if it failed */
+ public String userName;
+
+ /**
+ * If this is true then userName and hostType contain valid values. Else
+ * errorCode contain the error code, and errorMessage contains the
+ * corresponding message.
+ */
+ public boolean successful;
+
+ /** Error code */
+ public int errorCode;
+
+ /** Identd on port 113 can't be contacted */
+ public static final int ERR_NO_CONNECT = 1;
+
+ /** Connection timed out */
+ public static final int ERR_TIMEOUT = 2;
+
+ /**
+ * Identd daemon responded with ERROR, in this case errorMessage contains
+ * the string explanation, as send by the daemon.
+ */
+ public static final int ERR_PROTOCOL = 3;
+
+ /**
+ * When parsing server response protocol error happened.
+ */
+ public static final int ERR_PROTOCOL_INCORRECT = 4;
+
+ /**
+ * Maximum amount of time we should wait before dropping the connection to
+ * identd server.Setting it to 0 implies infinit timeout.
+ */
+ public static final int connectionTimeout = 10000;
+
+ /**
+ * Constructor tries to connect to Identd daemon on the host of the given
+ * socket, and retrieve user name of the owner of given socket connection on
+ * remote machine. After constructor returns public fields are initialised
+ * to whatever the server returned.
+ * <p>
+ * If user name was successfully retrieved successful is set to true, and
+ * userName and hostType are set to whatever server returned. If however for
+ * some reason user name was not obtained, successful is set to false and
+ * errorCode contains the code explaining the reason of failure, and
+ * errorMessage contains human readable explanation.
+ * <p>
+ * Constructor may block, for a while.
+ *
+ * @param s
+ * Socket whose ownership on remote end should be obtained.
+ */
+ public Ident(Socket s) {
+ Socket sock = null;
+ successful = false; // We are pessimistic
+
+ try {
+ sock = new Socket(s.getInetAddress(), 113);
+ sock.setSoTimeout(connectionTimeout);
+ final byte[] request = ("" + s.getPort() + " , " + s.getLocalPort() + "\r\n")
+ .getBytes();
+
+ sock.getOutputStream().write(request);
+
+ final BufferedReader in = new BufferedReader(new InputStreamReader(
+ sock.getInputStream()));
+
+ parseResponse(in.readLine());
+
+ } catch (final InterruptedIOException iioe) {
+ errorCode = ERR_TIMEOUT;
+ errorMessage = "Connection to identd timed out.";
+ } catch (final ConnectException ce) {
+ errorCode = ERR_NO_CONNECT;
+ errorMessage = "Connection to identd server failed.";
+
+ } catch (final IOException ioe) {
+ errorCode = ERR_NO_CONNECT;
+ errorMessage = "" + ioe;
+ } finally {
+ try {
+ if (sock != null) {
+ sock.close();
+ }
+ } catch (final IOException ioe) {
+ log.warn("Could not close socket", ioe);
+ }
+ }
+ }
+
+ private void parseResponse(String response) {
+ if (response == null) {
+ errorCode = ERR_PROTOCOL_INCORRECT;
+ errorMessage = "Identd server closed connection.";
+ return;
+ }
+
+ final StringTokenizer st = new StringTokenizer(response, ":");
+ if (st.countTokens() < 3) {
+ errorCode = ERR_PROTOCOL_INCORRECT;
+ errorMessage = "Can't parse server response.";
+ return;
+ }
+
+ st.nextToken(); // Discard first token, it's basically what we have send
+ final String command = st.nextToken().trim().toUpperCase();
+
+ if (command.equals("USERID") && (st.countTokens() >= 2)) {
+ successful = true;
+ hostType = st.nextToken().trim();
+ userName = st.nextToken("").substring(1);// Get all that is left
+ } else if (command.equals("ERROR")) {
+ errorCode = ERR_PROTOCOL;
+ errorMessage = st.nextToken();
+ } else {
+ errorCode = ERR_PROTOCOL_INCORRECT;
+ System.out.println("Opa!");
+ errorMessage = "Can't parse server response.";
+ }
+
+ }
+
+ // /////////////////////////////////////////////
+ // USED for Testing
+ /*
+ * public static void main(String[] args) throws IOException{
+ *
+ * Socket s = null; s = new Socket("gp101-16", 1391);
+ *
+ * Ident id = new Ident(s); if(id.successful){
+ * System.out.println("User: "+id.userName);
+ * System.out.println("HostType: "+id.hostType); }else{
+ * System.out.println("ErrorCode: "+id.errorCode);
+ * System.out.println("ErrorMessage: "+id.errorMessage);
+ *
+ * }
+ *
+ * if(s!= null) s.close(); } //
+ */
+
+}
diff --git a/src/com/runjva/sourceforge/jsocks/server/IdentAuthenticator.java b/src/com/runjva/sourceforge/jsocks/server/IdentAuthenticator.java
new file mode 100644
index 0000000..70e2593
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/server/IdentAuthenticator.java
@@ -0,0 +1,182 @@
+package com.runjva.sourceforge.jsocks.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import com.runjva.sourceforge.jsocks.protocol.InetRange;
+import com.runjva.sourceforge.jsocks.protocol.ProxyMessage;
+
+/**
+ * An implementation of socks.ServerAuthentication which provides simple
+ * authentication based on the host from which the connection is made and the
+ * name of the user on the remote machine, as reported by identd daemon on the
+ * remote machine.
+ * <p>
+ * It can also be used to provide authentication based only on the contacting
+ * host address.
+ */
+
+public class IdentAuthenticator extends ServerAuthenticatorBase {
+ /** Vector of InetRanges */
+ Vector<InetRange> hosts;
+
+ /** Vector of user hashes */
+ Vector<Hashtable<?, ?>> users;
+
+ String user;
+
+ /**
+ * Constructs empty IdentAuthenticator.
+ */
+ public IdentAuthenticator() {
+ hosts = new Vector<InetRange>();
+ users = new Vector<Hashtable<?, ?>>();
+ }
+
+ /**
+ * Used to create instances returned from startSession.
+ *
+ * @param in
+ * Input stream.
+ * @param out
+ * OutputStream.
+ * @param user
+ * Username associated with this connection,could be null if name
+ * was not required.
+ */
+ IdentAuthenticator(InputStream in, OutputStream out, String user) {
+ super(in, out);
+ this.user = user;
+ }
+
+ /**
+ * Adds range of addresses from which connection is allowed. Hashtable users
+ * should contain user names as keys and anything as values (value is not
+ * used and will be ignored).
+ *
+ * @param hostRange
+ * Range of ip addresses from which connection is allowed.
+ * @param users
+ * Hashtable of users for whom connection is allowed, or null to
+ * indicate that anybody is allowed to connect from the hosts
+ * within given range.
+ */
+ public synchronized void add(InetRange hostRange, Hashtable<?, ?> users) {
+ this.hosts.addElement(hostRange);
+ this.users.addElement(users);
+ }
+
+ /**
+ * Grants permission only to those users, who connect from one of the hosts
+ * registered with add(InetRange,Hashtable) and whose names, as reported by
+ * identd daemon, are listed for the host the connection came from.
+ */
+ public ServerAuthenticator startSession(Socket s) throws IOException {
+
+ final int ind = getRangeIndex(s.getInetAddress());
+ String user = null;
+
+ // System.out.println("getRangeReturned:"+ind);
+
+ if (ind < 0) {
+ return null; // Host is not on the list.
+ }
+
+ final ServerAuthenticator serverAuthenticator = super.startSession(s);
+ final ServerAuthenticatorBase auth = (ServerAuthenticatorBase) serverAuthenticator;
+
+ // System.out.println("super.startSession() returned:"+auth);
+ if (auth == null) {
+ return null;
+ }
+
+ // do the authentication
+
+ final Hashtable<?, ?> user_names = users.elementAt(ind);
+
+ if (user_names != null) { // If need to do authentication
+ Ident ident;
+ ident = new Ident(s);
+ // If can't obtain user name, fail
+ if (!ident.successful) {
+ return null;
+ }
+ // If user name is not listed for this address, fail
+ if (!user_names.containsKey(ident.userName)) {
+ return null;
+ }
+ user = ident.userName;
+ }
+ return new IdentAuthenticator(auth.in, auth.out, user);
+
+ }
+
+ /**
+ * For SOCKS5 requests allways returns true. For SOCKS4 requests checks
+ * wether the user name supplied in the request corresponds to the name
+ * obtained from the ident daemon.
+ */
+ public boolean checkRequest(ProxyMessage msg, java.net.Socket s) {
+ // If it's version 5 request, or if anybody is permitted, return true;
+ if ((msg.version == 5) || (user == null)) {
+ return true;
+ }
+
+ if (msg.version != 4) {
+ return false; // Who knows?
+ }
+
+ return user.equals(msg.user);
+ }
+
+ /** Get String representaion of the IdentAuthenticator. */
+ public String toString() {
+ String s = "";
+
+ for (int i = 0; i < hosts.size(); ++i) {
+ s += "(Range:" + hosts.elementAt(i) + "," + //
+ " Users:" + userNames(i) + ") ";
+ }
+ return s;
+ }
+
+ // Private Methods
+ // ////////////////
+ private int getRangeIndex(InetAddress ip) {
+ int index = 0;
+ final Enumeration<InetRange> enumx = hosts.elements();
+ while (enumx.hasMoreElements()) {
+ final InetRange ir = enumx.nextElement();
+ if (ir.contains(ip)) {
+ return index;
+ }
+ index++;
+ }
+ return -1; // Not found
+ }
+
+ private String userNames(int i) {
+ if (users.elementAt(i) == null) {
+ return "Everybody is permitted.";
+ }
+
+ final Enumeration<?> enumx = ((Hashtable<?, ?>) users.elementAt(i))
+ .keys();
+ if (!enumx.hasMoreElements()) {
+ return "";
+ }
+ String s = enumx.nextElement().toString();
+ while (enumx.hasMoreElements()) {
+ s += "; " + enumx.nextElement();
+ }
+
+ return s;
+ }
+
+}
diff --git a/src/com/runjva/sourceforge/jsocks/server/ServerAuthenticator.java b/src/com/runjva/sourceforge/jsocks/server/ServerAuthenticator.java
new file mode 100644
index 0000000..3014a92
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/server/ServerAuthenticator.java
@@ -0,0 +1,126 @@
+package com.runjva.sourceforge.jsocks.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.DatagramPacket;
+import java.net.Socket;
+
+import com.runjva.sourceforge.jsocks.protocol.ProxyMessage;
+import com.runjva.sourceforge.jsocks.protocol.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/com/runjva/sourceforge/jsocks/server/ServerAuthenticatorBase.java b/src/com/runjva/sourceforge/jsocks/server/ServerAuthenticatorBase.java
new file mode 100644
index 0000000..79acd58
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/server/ServerAuthenticatorBase.java
@@ -0,0 +1,187 @@
+package com.runjva.sourceforge.jsocks.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PushbackInputStream;
+import java.net.Socket;
+
+import com.runjva.sourceforge.jsocks.protocol.ProxyMessage;
+import com.runjva.sourceforge.jsocks.protocol.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 abstract class ServerAuthenticatorBase implements ServerAuthenticator {
+
+ static final byte[] socks5response = { 5, 0 };
+
+ InputStream in;
+ OutputStream out;
+
+ /**
+ * Creates new instance of the ServerAuthenticatorNone.
+ */
+ public ServerAuthenticatorBase() {
+ 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 ServerAuthenticatorBase(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 {
+
+ final PushbackInputStream in = new PushbackInputStream(s
+ .getInputStream());
+ final OutputStream out = s.getOutputStream();
+
+ final int version = in.read();
+ if (version == 5) {
+ if (!selectSocks5Authentication(in, out, 0)) {
+ return null;
+ }
+ } else if (version == 4) {
+ // Else it is the request message already, 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 {
+
+ final int num_methods = in.read();
+ if (num_methods <= 0) {
+ return false;
+ }
+ final byte method_ids[] = new byte[num_methods];
+ final 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/com/runjva/sourceforge/jsocks/server/ServerAuthenticatorNone.java b/src/com/runjva/sourceforge/jsocks/server/ServerAuthenticatorNone.java
new file mode 100644
index 0000000..0e97677
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/server/ServerAuthenticatorNone.java
@@ -0,0 +1,16 @@
+package com.runjva.sourceforge.jsocks.server;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Simplest possible ServerAuthenticator implementation. Extends common base.
+ *
+ */
+public class ServerAuthenticatorNone extends ServerAuthenticatorBase {
+
+ public ServerAuthenticatorNone(InputStream in, OutputStream out) {
+ super(in, out);
+ }
+
+}
diff --git a/src/com/runjva/sourceforge/jsocks/server/UserPasswordAuthenticator.java b/src/com/runjva/sourceforge/jsocks/server/UserPasswordAuthenticator.java
new file mode 100644
index 0000000..82980f2
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/server/UserPasswordAuthenticator.java
@@ -0,0 +1,82 @@
+package com.runjva.sourceforge.jsocks.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+/**
+ * This class implements SOCKS5 User/Password authentication scheme as defined
+ * in rfc1929,the server side of it. (see docs/rfc1929.txt)
+ */
+public class UserPasswordAuthenticator extends ServerAuthenticatorBase {
+
+ static final int METHOD_ID = 2;
+
+ UserValidation validator;
+
+ /**
+ * Construct a new UserPasswordAuthentication object, with given
+ * UserVlaidation scheme.
+ *
+ * @param v
+ * UserValidation to use for validating users.
+ */
+ public UserPasswordAuthenticator(UserValidation validator) {
+ this.validator = validator;
+ }
+
+ public ServerAuthenticator startSession(Socket s) throws IOException {
+ final InputStream in = s.getInputStream();
+ final OutputStream out = s.getOutputStream();
+
+ if (in.read() != 5) {
+ return null; // Drop non version 5 messages.
+ }
+
+ if (!selectSocks5Authentication(in, out, METHOD_ID)) {
+ return null;
+ }
+ if (!doUserPasswordAuthentication(s, in, out)) {
+ return null;
+ }
+
+ return new ServerAuthenticatorNone(in, out);
+ }
+
+ // Private Methods
+ // ////////////////
+
+ private boolean doUserPasswordAuthentication(Socket s, InputStream in,
+ OutputStream out) throws IOException {
+ final int version = in.read();
+ if (version != 1) {
+ return false;
+ }
+
+ final int ulen = in.read();
+ if (ulen < 0) {
+ return false;
+ }
+
+ final byte[] user = new byte[ulen];
+ in.read(user);
+ final int plen = in.read();
+ if (plen < 0) {
+ return false;
+ }
+ final byte[] password = new byte[plen];
+ in.read(password);
+
+ if (validator.isUserValid(new String(user), new String(password), s)) {
+ // System.out.println("user valid");
+ out.write(new byte[] { 1, 0 });
+ } else {
+ // System.out.println("user invalid");
+ out.write(new byte[] { 1, 1 });
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/com/runjva/sourceforge/jsocks/server/UserValidation.java b/src/com/runjva/sourceforge/jsocks/server/UserValidation.java
new file mode 100644
index 0000000..c4f7770
--- /dev/null
+++ b/src/com/runjva/sourceforge/jsocks/server/UserValidation.java
@@ -0,0 +1,24 @@
+package com.runjva.sourceforge.jsocks.server;
+
+/**
+ * Interface which provides for user validation, based on user name password and
+ * where it connects from.
+ */
+public interface UserValidation {
+ /**
+ * Implementations of this interface are expected to use some or all of the
+ * information provided plus any information they can extract from other
+ * sources to decide wether given user should be allowed access to SOCKS
+ * server, or whatever you use it for.
+ *
+ * @return true to indicate user is valid, false otherwise.
+ * @param username
+ * User whom implementation should validate.
+ * @param password
+ * Password this user provided.
+ * @param connection
+ * Socket which user used to connect to the server.
+ */
+ boolean isUserValid(String username, String password,
+ java.net.Socket connection);
+}
diff --git a/src/org/torproject/android/Orbot.java b/src/org/torproject/android/Orbot.java
index 95a5f16..0ffdecf 100644
--- a/src/org/torproject/android/Orbot.java
+++ b/src/org/torproject/android/Orbot.java
@@ -13,9 +13,11 @@ import org.torproject.android.service.TorService;
import org.torproject.android.service.TorServiceConstants;
import org.torproject.android.service.TorServiceUtils;
import org.torproject.android.settings.SettingsPreferences;
+import org.torproject.android.vpn.OrbotVpnService;
import org.torproject.android.wizard.ChooseLocaleWizardActivity;
import org.torproject.android.wizard.TipsAndTricks;
+import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ComponentName;
@@ -32,6 +34,8 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.net.Uri;
+import android.net.VpnService;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -412,6 +416,10 @@ public class Orbot extends ActionBarActivity implements TorConstants, OnLongClic
}
+ else if (item.getItemId() == R.id.menu_vpn)
+ {
+ this.startVpnService();
+ }
return true;
}
@@ -782,9 +790,18 @@ public class Orbot extends ActionBarActivity implements TorConstants, OnLongClic
startActivityForResult(new Intent(this, SettingsPreferences.class), 1);
}
+ private final static int REQUEST_VPN = 8888;
-
-
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ public void startVpnService () {
+ Intent intent = VpnService.prepare(this);
+ if (intent != null) {
+ startActivityForResult(intent, REQUEST_VPN);
+ } else {
+ onActivityResult(REQUEST_VPN, RESULT_OK, null);
+ }
+ }
+
@Override
protected void onActivityResult(int request, int response, Intent data) {
@@ -827,6 +844,11 @@ public class Orbot extends ActionBarActivity implements TorConstants, OnLongClic
}
}
+ else if (request == REQUEST_VPN && response == RESULT_OK)
+ {
+ Intent intent = new Intent(this, OrbotVpnService.class);
+ startService(intent);
+ }
}
@@ -1207,7 +1229,6 @@ public class Orbot extends ActionBarActivity implements TorConstants, OnLongClic
private final ServiceConnection mConnection = new ServiceConnection() {
-
public void onServiceConnected(ComponentName className,
IBinder service) {
diff --git a/src/org/torproject/android/vpn/OrbotVpnService.java b/src/org/torproject/android/vpn/OrbotVpnService.java
new file mode 100644
index 0000000..1d06723
--- /dev/null
+++ b/src/org/torproject/android/vpn/OrbotVpnService.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.torproject.android.vpn;
+
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.channels.DatagramChannel;
+import java.util.Locale;
+
+import org.torproject.android.service.TorServiceConstants;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.VpnService;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.runjva.sourceforge.jsocks.protocol.ProxyServer;
+import com.runjva.sourceforge.jsocks.server.ServerAuthenticatorNone;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class OrbotVpnService extends VpnService implements Handler.Callback {
+ private static final String TAG = "OrbotVpnService";
+
+ private PendingIntent mConfigureIntent;
+
+ private Handler mHandler;
+ private Thread mThread;
+
+ private String mSessionName = "OrbotVPN";
+ private ParcelFileDescriptor mInterface;
+
+ private int mSocksProxyPort = 9999;
+ private ProxyServer mProxyServer;
+
+ private boolean mKeepRunning = true;
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // The handler is only used to show messages.
+ if (mHandler == null) {
+ mHandler = new Handler(this);
+ }
+
+ // Stop the previous session by interrupting the thread.
+ if (mThread != null) {
+ mThread.interrupt();
+ }
+
+ startSocksBypass ();
+ setupTun2Socks();
+
+ return START_STICKY;
+ }
+
+ private void startSocksBypass ()
+ {
+ Thread thread = new Thread ()
+ {
+ public void run ()
+ {
+
+ try {
+ mProxyServer = new ProxyServer(new ServerAuthenticatorNone(null, null));
+ mProxyServer.setVpnService(OrbotVpnService.this);
+ mProxyServer.start(mSocksProxyPort, 5, InetAddress.getLocalHost());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ thread.start();
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mThread != null) {
+ mKeepRunning = false;
+ mThread.interrupt();
+ }
+
+ if (mProxyServer != null)
+ mProxyServer.stop();
+ }
+
+ @Override
+ public boolean handleMessage(Message message) {
+ if (message != null) {
+ Toast.makeText(this, message.what, Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ }
+
+
+ private void setupTun2Socks() {
+
+ if (mInterface == null)
+ {
+ // Set the locale to English (or probably any other language that^M
+ // uses Hindu-Arabic (aka Latin) numerals).^M
+ // We have found that VpnService.Builder does something locale-dependent^M
+ // internally that causes errors when the locale uses its own numerals^M
+ // (i.e., Farsi and Arabic).^M
+ Locale.setDefault(new Locale("en"));
+
+ Builder builder = new Builder();
+
+ builder.setMtu(3000);
+ builder.addAddress("10.0.0.1",8);
+ builder.setSession("OrbotVPN");
+ builder.addRoute("0.0.0.0",0);
+ builder.addRoute("10.0.0.0",8);
+ //builder.addDnsServer("10.0.0.2");
+ builder.addDnsServer("127.0.0.1");
+ // Close the old interface since the parameters have been changed.
+ try {
+ mInterface.close();
+ } catch (Exception e) {
+ // ignore
+ }
+
+
+ // Create a new interface using the builder and save the parameters.
+ mInterface = builder.setSession(mSessionName)
+ .setConfigureIntent(mConfigureIntent)
+ .establish();
+
+ Tun2Socks.Start(mInterface, 3000, "10.0.0.2", "255.255.255.0", "localhost:" + TorServiceConstants.PORT_SOCKS_DEFAULT, null, true);
+ }
+ }
+
+ private void debugPacket(ByteBuffer packet)
+ {
+
+ int buffer = packet.get();
+ int version;
+ int headerlength;
+ version = buffer >> 4;
+ headerlength = buffer & 0x0F;
+ headerlength *= 4;
+ Log.d(TAG, "IP Version:"+version);
+ Log.d(TAG, "Header Length:"+headerlength);
+
+ String status = "";
+ status += "Header Length:"+headerlength;
+
+ buffer = packet.get(); //DSCP + EN
+ buffer = packet.getChar(); //Total Length
+
+ Log.d(TAG, "Total Length:"+buffer);
+
+ buffer = packet.getChar(); //Identification
+ Log.d(TAG, "Identification:"+buffer);
+
+ buffer = packet.getChar(); //Flags + Fragment Offset
+ buffer = packet.get(); //Time to Live
+ buffer = packet.get(); //Protocol
+
+ Log.d(TAG, "Protocol:"+buffer);
+
+ status += " Protocol:"+buffer;
+
+ buffer = packet.getChar(); //Header checksum
+
+ String sourceIP = "";
+ buffer = packet.get(); //Source IP 1st Octet
+ sourceIP += buffer;
+ sourceIP += ".";
+
+ buffer = packet.get(); //Source IP 2nd Octet
+ sourceIP += buffer;
+ sourceIP += ".";
+
+ buffer = packet.get(); //Source IP 3rd Octet
+ sourceIP += buffer;
+ sourceIP += ".";
+
+ buffer = packet.get(); //Source IP 4th Octet
+ sourceIP += buffer;
+
+ Log.d(TAG, "Source IP:"+sourceIP);
+
+ status += " Source IP:"+sourceIP;
+
+ String destIP = "";
+ buffer = packet.get(); //Destination IP 1st Octet
+ destIP += buffer;
+ destIP += ".";
+
+ buffer = packet.get(); //Destination IP 2nd Octet
+ destIP += buffer;
+ destIP += ".";
+
+ buffer = packet.get(); //Destination IP 3rd Octet
+ destIP += buffer;
+ destIP += ".";
+
+ buffer = packet.get(); //Destination IP 4th Octet
+ destIP += buffer;
+
+ Log.d(TAG, "Destination IP:"+destIP);
+
+ status += " Destination IP:"+destIP;
+ /*
+ msgObj = mHandler.obtainMessage();
+ msgObj.obj = status;
+ mHandler.sendMessage(msgObj);
+ */
+
+ //Log.d(TAG, "version:"+packet.getInt());
+ //Log.d(TAG, "version:"+packet.getInt());
+ //Log.d(TAG, "version:"+packet.getInt());
+
+ }
+
+}
diff --git a/src/org/torproject/android/vpn/Tun2Socks.java b/src/org/torproject/android/vpn/Tun2Socks.java
new file mode 100644
index 0000000..b921631
--- /dev/null
+++ b/src/org/torproject/android/vpn/Tun2Socks.java
@@ -0,0 +1,124 @@
+package org.torproject.android.vpn;
+
+/*
+ * Copyright (c) 2013, Psiphon Inc.
+ * All rights reserved.
+ *
+ * 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/>.
+ *
+ */
+
+import java.net.DatagramSocket;
+import java.net.Socket;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+public class Tun2Socks
+{
+ public static interface IProtectSocket
+ {
+ boolean doVpnProtect(Socket socket);
+ boolean doVpnProtect(DatagramSocket socket);
+ };
+
+
+ private static Thread mThread;
+ private static ParcelFileDescriptor mVpnInterfaceFileDescriptor;
+ private static int mVpnInterfaceMTU;
+ private static String mVpnIpAddress;
+ private static String mVpnNetMask;
+ private static String mSocksServerAddress;
+ private static String mUdpgwServerAddress;
+ private static boolean mUdpgwTransparentDNS;
+
+ // Note: this class isn't a singleton, but you can't run more
+ // than one instance due to the use of global state (the lwip
+ // module, etc.) in the native code.
+
+ public static synchronized void Start(
+ ParcelFileDescriptor vpnInterfaceFileDescriptor,
+ int vpnInterfaceMTU,
+ String vpnIpAddress,
+ String vpnNetMask,
+ String socksServerAddress,
+ String udpgwServerAddress,
+ boolean udpgwTransparentDNS)
+ {
+
+
+ Stop();
+
+ mVpnInterfaceFileDescriptor = vpnInterfaceFileDescriptor;
+ mVpnInterfaceMTU = vpnInterfaceMTU;
+ mVpnIpAddress = vpnIpAddress;
+ mVpnNetMask = vpnNetMask;
+ mSocksServerAddress = socksServerAddress;
+ mUdpgwServerAddress = udpgwServerAddress;
+ mUdpgwTransparentDNS = udpgwTransparentDNS;
+
+ mThread = new Thread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ runTun2Socks(
+ mVpnInterfaceFileDescriptor.detachFd(),
+ mVpnInterfaceMTU,
+ mVpnIpAddress,
+ mVpnNetMask,
+ mSocksServerAddress,
+ mUdpgwServerAddress,
+ mUdpgwTransparentDNS ? 1 : 0);
+
+ }
+ });
+ mThread.start();
+ }
+
+ public static synchronized void Stop()
+ {
+ if (mThread != null)
+ {
+ terminateTun2Socks();
+ try
+ {
+ mThread.join();
+ }
+ catch (InterruptedException e)
+ {
+ Thread.currentThread().interrupt();
+ }
+ mThread = null;
+ }
+ }
+
+ private native static int runTun2Socks(
+ int vpnInterfaceFileDescriptor,
+ int vpnInterfaceMTU,
+ String vpnIpAddress,
+ String vpnNetMask,
+ String socksServerAddress,
+ String udpgwServerAddress,
+ int udpgwTransparentDNS);
+
+ private native static void terminateTun2Socks();
+
+ static
+ {
+ System.loadLibrary("tun2socks");
+ }
+}
\ No newline at end of file
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits