diff --git a/external/Makefile b/external/Makefile index df7de72c..8e0e0826 100644 --- a/external/Makefile +++ b/external/Makefile @@ -52,7 +52,7 @@ STRIP := $(NDK_TOOLCHAIN_BASE)/bin/$(HOST)-strip \ --strip-unneeded -R .note -R .comment # PIEFLAGS for SDK 16/Android L must be set to -fPIE -pie -PIEFLAGS? = +PIEFLAGS = -fPIE -pie CFLAGS = -DANDROID $(TARGET_CFLAGS) $(PIEFLAGS) LDFLAGS = -llog $(TARGET_LDFLAGS) $(PIEFLAGS) diff --git a/jni/Application.mk b/jni/Application.mk new file mode 100644 index 00000000..0cfcc0f5 --- /dev/null +++ b/jni/Application.mk @@ -0,0 +1,3 @@ +APP_OPTIM := release +APP_ABI := all #armeabi armeabi-v7a mips x86 +APP_PLATFORM := android-14 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 00000000..9135f871 --- /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 uhash = new Hashtable(); + + 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 00000000..701d39a6 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 00000000..70d0a941 --- /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. + * + *

+ * 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 00000000..e6821545 --- /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 00000000..fae13587 --- /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. + *

+ * 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 host_names; + Vector all; + Vector end_names; + + boolean useSeparateThread = true; + + /** + * Creates the empty range. + */ + public InetRange() { + all = new Vector(); + host_names = new Hashtable(); + end_names = new Vector(); + } + + /** + * Adds another host or range to this range. The String can be one of those: + *

+ */ + 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. + *

+ * Algorithm:
+ *

    + *
  1. Look up if the hostname is in the range (in the Hashtable). + *
  2. Check if it ends with one of the speciefied endings. + *
  3. Check if it is ip(eg.130.220.35.98). If it is check if it is in the + * range. + *
  4. 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. + *
+ * + * @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.
+ * 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.
+ * + * @param s + * Entry to remove. + * @return true if successfull. + */ + public synchronized boolean remove(final String s) { + final Enumeration 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) all.clone(); + new_range.end_names = (Vector) end_names.clone(); + new_range.host_names = (Hashtable) 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 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 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 + * 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. + *

+ * 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.
+ * Zero timeout implies infinity.
+ * 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.
+ * Zero timeout implies infinity.
+ * Default timeout is 3 minutes. + */ + public static void setAcceptTimeout(final int timeout) { + acceptTimeout = timeout; + } + + /** + * Sets the timeout for UDPRelay server.
+ * Zero timeout implies infinity.
+ * 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.
+ * 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.
+ * 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.
+ * 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 00000000..484ad969 --- /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 00000000..ac4363bb --- /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 00000000..6dcaf44c --- /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.
+ * 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. + *

+ * SOCKS5 protocol allows to send host address as either: + *

    + *
  • IPV4, normal 4 byte address. (10 bytes header size) + *
  • IPV6, version 6 ip address (not supported by Java as for now). 22 bytes + * header size. + *
  • Host name,(7+length of the host name bytes header size). + *
+ * As with other Socks equivalents, direct addresses are handled transparently, + * that is data will be send directly when required by the proxy settings. + *

+ * NOTE:
+ * Unlike other SOCKS Sockets, it does not 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: + *

    + *
  1. Given version of proxy does not support UDP_ASSOCIATE. + *
  2. Proxy can't be reached. + *
  3. Authorization fails. + *
  4. Proxy does not want to perform udp forwarding, for any reason. + *
+ * 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.
+ * + * NOTE: DatagramPacket size should be at least 10 bytes less than + * the systems limit. + * + *

+ * 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. + *

+ * 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.
+ * If the packet arrived from anywhere else it is not changed.
+ * NOTE: 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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> 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 00000000..0f24715b --- /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 00000000..7400aa6e --- /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 authMethods = new Hashtable(); + 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. + *

+ * 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) 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 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 00000000..a6515974 --- /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. + *

+ * 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 00000000..c4ce565b --- /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. + *

+ * Defaults: port = 1080.
+ * 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 00000000..6d812f29 --- /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. + *

+ * 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 00000000..fd626ec4 --- /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. + *

+ * Using Socks package can be as easy as that: + * + *

+ * <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>
+ * 
+ *

+ * 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: + *

    + * + *
  1. Proxy settings say that address should be resolved + * locally, but this fails. + *
  2. Proxy settings say that the host should be contacted + * directly but host name can't be resolved. + *
+ * @throws SocksException + * If one of the following happens: + *
    + *
  • Proxy is is null. + *
  • Proxy settings say that the host should be contacted + * directly but this fails. + *
  • Socks Server can't be contacted. + *
  • Authentication fails. + *
  • Connection is not allowed by the SOCKS proxy. + *
  • SOCKS proxy can't establish the connection. + *
  • Any IO error occured. + *
  • Any protocol error occured. + *
+ * @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 00000000..913bb31c --- /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. + *

+ * 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 00000000..14599c7e --- /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.
+ * Zero timeout implies infinity.
+ * Default timeout is 3 minutes. + */ + + static public void setTimeout(int timeout) { + iddleTimeout = timeout; + } + + /** + * Sets the size of the datagrams used in the UDPRelayServer.
+ * 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. + *

+ * 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 00000000..c161d334 --- /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 00000000..7193f798 --- /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. + *

+ * To use it:

+   Socket s = ss.accept();
+   Ident id = new Ident(s);
+   if(id.successful) goUseUser(id.userName);
+   else handleIdentError(id.errorCode,id.errorMessage)
+   
+ */ +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. + *

+ * 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. + *

+ * 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 00000000..da040f55 --- /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. + *

+ * It can also be used to provide authentication based only on the contacting + * host address. + */ + +public class IdentAuthenticator extends ServerAuthenticatorBase { + /** Vector of InetRanges */ + Vector hosts; + + /** Vector of user hashes */ + Vector> users; + + String user; + + /** + * Constructs empty IdentAuthenticator. + */ + public IdentAuthenticator() { + hosts = new Vector(); + users = new Vector>(); + } + + /** + * 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 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 00000000..c2451ac5 --- /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. + *

+ * 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. + *

+ * 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. + *

+ * SOCKSv5 allows to have multiple authentication methods, and these methods + * might require some kind of transformations being made on the data. + *

+ * 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. + *

+ * SOCKSv5 allows to have multiple authentication methods, and these methods + * might require some kind of transformations being made on the data. + *

+ * 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. + *

+ * If no transformation should be done on the datagrams, this method should + * return null. + *

+ * This method is called on the object returned from the startSession + * function. + */ + + UDPEncapsulation getUdpEncapsulation(); + + /** + * This method is called when a request have been read. + *

+ * Implementation should decide wether to grant request or not. Returning + * true implies granting the request, false means request should be + * rejected. + *

+ * 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. + *

+ * 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. + *

+ * Implementaions should return true if the datagram is to be forwarded, and + * false if the datagram should be dropped. + *

+ * 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. + *

+ * 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 00000000..c67a89f6 --- /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 not do any + * authentication. + *

+ * Warning!!
+ * Should not be used on machines which are not behind the firewall. + *

+ * It is only provided to make implementing other authentication schemes easier. + *
+ * For Example:

+   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;
+    }
+   }
+
+ */ +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. + *

+ * 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 00000000..0e976773 --- /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 00000000..7d54b45b --- /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 00000000..d0b52290 --- /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/sandroproxy/ony/OrbotApp.java b/src/org/sandroproxy/ony/OrbotApp.java new file mode 100644 index 00000000..fdc3b895 --- /dev/null +++ b/src/org/sandroproxy/ony/OrbotApp.java @@ -0,0 +1,69 @@ +package org.sandroproxy.ony; + +import java.util.Locale; + +import org.torproject.android.TorConstants; +import org.torproject.android.service.TorServiceUtils; + +import android.app.Application; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.preference.PreferenceManager; + +public class OrbotApp extends Application implements TorConstants +{ + + private Locale locale; + private final static String DEFAULT_LOCALE = "en"; + private SharedPreferences settings; + + @Override + public void onCreate() { + super.onCreate(); + + settings = TorServiceUtils.getSharedPrefs(getApplicationContext()); + + Configuration config = getResources().getConfiguration(); + + String lang = settings.getString(PREF_DEFAULT_LOCALE, DEFAULT_LOCALE); + + if (! "".equals(lang) && ! config.locale.getLanguage().equals(lang)) + { + if (lang.equals("xx")) + { + locale = Locale.getDefault(); + + } + else + locale = new Locale(lang); + + Locale.setDefault(locale); + + Configuration myConfig = new Configuration(config); + myConfig.locale = locale; + + getResources().updateConfiguration(myConfig, getResources().getDisplayMetrics()); + } + + + } + + @Override + public void onConfigurationChanged(Configuration newConfig) + { + super.onConfigurationChanged(newConfig); + + String lang = settings.getString(PREF_DEFAULT_LOCALE, DEFAULT_LOCALE); + + if (! "".equals(lang) && ! newConfig.locale.getLanguage().equals(lang)) + { + locale = new Locale(lang); + Locale.setDefault(locale); + + Configuration myConfig = new Configuration(newConfig); + myConfig.locale = locale; + + getResources().updateConfiguration(myConfig, getResources().getDisplayMetrics()); + } + } +}