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;
}
}
}