package com.limegroup.gnutella.util; import java.io.IOException; import java.io.BufferedReader; import java.io.OutputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.net.InetAddress; import java.net.Socket; import java.net.InetSocketAddress; import com.limegroup.gnutella.settings.ConnectionSettings; import com.limegroup.gnutella.io.ConnectObserver; import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.Log; /** * A collection of utilities for proxy connection establishment. * Used with Sockets. */ class ProxyUtils { private static final Log LOG = LogFactory.getLog(ProxyUtils.class); private ProxyUtils() {} /** * Determines the kind of proxy to use for connecting to the given address. */ static int getProxyType(InetAddress address) { // if the user specified that he wanted to use a proxy to connect // to the network, we will use that proxy unless the host we // want to connect to is a private address int connectionType = ConnectionSettings.CONNECTION_METHOD.getValue(); boolean valid = connectionType != ConnectionSettings.C_NO_PROXY && (!NetworkUtils.isPrivateAddress(address) || ConnectionSettings.USE_PROXY_FOR_PRIVATE.getValue()); if(valid) return connectionType; else return ConnectionSettings.C_NO_PROXY; } /** * Establishes a proxy connection on the given socket. */ static Socket establishProxy(int type, Socket proxySocket, InetSocketAddress addr, int timeout) throws IOException { switch(type) { case ConnectionSettings.C_HTTP_PROXY: return establishHTTPProxy(proxySocket, addr, timeout); case ConnectionSettings.C_SOCKS4_PROXY: return establishSocksV4(proxySocket, addr, timeout); case ConnectionSettings.C_SOCKS5_PROXY: return establishSocksV5(proxySocket, addr, timeout); default: throw new IOException("Unknown proxy type."); } } /** * Establishes a connection with a SOCKS V4 proxy. */ private static Socket establishSocksV4(Socket proxySocket, InetSocketAddress addr, int timeout) throws IOException { byte[] hostBytes = addr.getAddress().getAddress(); int port = addr.getPort(); byte[] portBytes = new byte[2]; portBytes[0] = ((byte) (port >> 8)); portBytes[1] = ((byte) port); OutputStream os = proxySocket.getOutputStream(); InputStream in = proxySocket.getInputStream(); proxySocket.setSoTimeout(timeout); os.write(0x04); //version os.write(0x01); //connect command os.write(portBytes); //port to connect to os.write(hostBytes); //host to connect to //write user name if necessary if (ConnectionSettings.PROXY_AUTHENTICATE.getValue()) os.write(ConnectionSettings.PROXY_USERNAME.getValue().getBytes()); os.write(0x00); //terminating 0 os.flush(); // read response // version should be 0 but some socks proxys answer 4 int version = in.read(); if (version != 0x00 && version != 0x04) throw new IOException("Invalid version from socks proxy: " + version + " expected 0 or 4"); // read the status, 0x5A is success int status = in.read(); if (status != 0x5A) throw new IOException("Request rejected with status: " + status); // the socks proxy will now send the connected port and host // we don't really check if it's the right one. byte[] connectedHostPort = new byte[2]; byte[] connectedHostAddress = new byte[4]; if (in.read(connectedHostPort) == -1 || in.read(connectedHostAddress) == -1) throw new IOException("Connection failed"); proxySocket.setSoTimeout(0); return proxySocket; } /** * Establishes a connection with a SOCKS V5 proxy. */ private static Socket establishSocksV5(Socket proxySocket, InetSocketAddress addr, int timeout) throws IOException { byte[] hostBytes = addr.getAddress().getAddress(); int port = addr.getPort(); byte[] portBytes = new byte[2]; portBytes[0] = ((byte) (port >> 8)); portBytes[1] = ((byte) port); OutputStream os = proxySocket.getOutputStream(); InputStream in = proxySocket.getInputStream(); proxySocket.setSoTimeout(timeout); os.write(0x05); //version if (ConnectionSettings.PROXY_AUTHENTICATE.getValue()) { os.write(0x02); //the number of authentication methods we support os.write(0x00); //authentication method: no authentication os.write(0x02); //authentication method: username/password } else { os.write(0x01); //the number of authentication methods we support os.write(0x00); //authentication method: no authentication } os.flush(); int version = in.read(); if (version != 0x05) throw new IOException("Invalid version from socks proxy: " + version + " expected 5"); int auth_method = in.read(); if (auth_method == 0x00) { // no authentication } else if (auth_method == 0x02) { // username/password String username = ConnectionSettings.PROXY_USERNAME.getValue(); String password = ConnectionSettings.PROXY_PASS.getValue(); os.write(0x01); // version of authentication protocol os.write((byte) username.length()); // length of username field os.write(username.getBytes()); // username os.write((byte) password.length()); // length of password field os.write(password.getBytes()); // password os.flush(); // read version for auth protocol from proxy, expects 1 version = in.read(); if (version != 0x01) throw new IOException("Invalid version for authentication: " + version + " expected 1"); // read status, 0 is success int status = in.read(); if (status != 0x00) throw new IOException("Authentication failed with status: " + status); } // request connection os.write(0x05); // version again os.write(0x01); // connect command, // 0x02 would be bind, 0x03 UDP associate os.write(0x00); // reserved field, must be 0x00 os.write(0x01); // address type: 0x01 is IPv4, 0x04 would be IPv6 os.write(hostBytes); //host to connect to os.write(portBytes); //port to connect to os.flush(); // read response // version should be 0x05 version = in.read(); if (version != 0x05) throw new IOException("Invalid version from socks proxy: " + version + " expected 5"); // read the status, 0x00 is success int status = in.read(); if (status != 0x00) throw new IOException("Request rejected with status: " + status); // skip reserved byte; in.read(); // read the address type in the reply and skip it. int addrType = in.read(); int bytesToSkip = 0; if (addrType == 1) { // IPv4 bytesToSkip = 4 + 2; } else if (addrType == 3) { // domain name bytesToSkip = in.read() + 2; } else if (addrType == 4) { // IPv6 bytesToSkip = 16 + 2; } for (int i = 0; i < bytesToSkip; i++) { if (in.read() == -1) throw new IOException("Connection failed"); } proxySocket.setSoTimeout(0); return proxySocket; } /** * Establishes a connection with an HTTP proxy. */ private static Socket establishHTTPProxy(Socket proxySocket, InetSocketAddress addr, int timeout) throws IOException { proxySocket.setSoTimeout(timeout); OutputStream os = proxySocket.getOutputStream(); InputStream in = proxySocket.getInputStream(); // write connection string String connectString = "CONNECT " + addr.getAddress().getHostAddress() + ":" + addr.getPort() + " HTTP/1.0\r\n\r\n"; os.write(connectString.getBytes()); os.flush(); // read response; BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line = reader.readLine(); // look for code 200 if (line==null || line.indexOf("200") == -1) throw new IOException("HTTP connection failed"); // skip the rest of the response while (!line.equals("")) { line = reader.readLine(); if(line == null) throw new IOException("end of stream"); } // we should be connected now proxySocket.setSoTimeout(0); return proxySocket; } /** * ConnectObserver that will establish a proxy prior to delegating the connect back * to the delegate. */ static class ProxyConnector implements ConnectObserver { private final int proxyType; private final ConnectObserver delegate; private final InetSocketAddress addr; private final int timeout; ProxyConnector(int type, ConnectObserver observer, InetSocketAddress host, int tout) { proxyType = type; delegate = observer; addr = host; timeout = tout; } public void handleConnect(final Socket s) throws IOException { ThreadFactory.startThread(new Runnable() { public void run() { try { establishProxy(proxyType, s, addr, timeout); } catch(IOException iox) { LOG.warn("Error establishing proxy connection", iox); IOUtils.close(s); shutdown(); // couldn't establish, so let delegate know. return; } try { delegate.handleConnect(s); } catch(IOException iox) { LOG.warn("Delegate IOX", iox); IOUtils.close(s); // do not call shutdown, because then the delegate // would get both handleConnect & shutdown, which // is confusing. } } }, "ProxyConnector"); } public void shutdown() { delegate.shutdown(); } // Ignored. public void handleIOException(IOException iox) {} public ConnectObserver getDelegateObserver() { return delegate; } } }