package net.sourceforge.jsocks;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Hashtable;
/**
* SOCKS5 Proxy.
*/
public class Socks5Proxy extends Proxy implements Cloneable {
// Data members
private Hashtable<Integer, Authentication> authMethods = new Hashtable<Integer, Authentication>();
private int selectedMethod;
boolean resolveAddrLocally = true;
UDPEncapsulation udp_encapsulation = null;
// Public Constructors
// ====================
/**
* Creates SOCKS5 proxy.
*
* @param proxyIP
* Host on which a Proxy server runs.
* @param proxyPort
* Port on which a Proxy server listens for connections.
*/
public Socks5Proxy(InetAddress proxyIP, int proxyPort) {
super(proxyIP, proxyPort);
version = 5;
setAuthenticationMethod(0, new AuthenticationNone());
}
/**
* Creates SOCKS5 proxy.
*
* @param proxyHost
* Host on which a Proxy server runs.
* @param proxyPort
* Port on which a Proxy server listens for connections.
* @throws UnknownHostException
* If proxyHost can't be resolved.
*/
public Socks5Proxy(String proxyHost, int proxyPort)
throws UnknownHostException {
super(proxyHost, proxyPort);
version = 5;
setAuthenticationMethod(0, new AuthenticationNone());
}
// Public instance methods
// ========================
/**
* Creates a clone of this Proxy.
*/
@Override
@SuppressWarnings("unchecked")
public Object clone() {
Socks5Proxy newProxy = new Socks5Proxy(proxyIP, proxyPort);
newProxy.authMethods = (Hashtable<Integer, Authentication>) this.authMethods
.clone();
newProxy.resolveAddrLocally = resolveAddrLocally;
newProxy.chainProxy = chainProxy;
return newProxy;
}
@Override
protected Proxy copy() {
Socks5Proxy copy = new Socks5Proxy(proxyIP, proxyPort);
copy.authMethods = this.authMethods; // same Hash, no copy
copy.chainProxy = this.chainProxy;
copy.resolveAddrLocally = this.resolveAddrLocally;
return copy;
}
@Override
protected ProxyMessage formMessage(InputStream in) throws SocksException,
IOException {
return new Socks5Message(in);
}
@Override
protected ProxyMessage formMessage(int cmd, InetAddress ip, int port) {
return new Socks5Message(cmd, ip, port);
}
@Override
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);
}
// Public Static(Class) Methods
// ==============================
// Protected Methods
// =================
/**
* Get authentication method, which corresponds to given method id
*
* @param methodId
* Authentication method id.
* @return Implementation for given method or null, if one was not set.
*/
public Authentication getAuthenticationMethod(int methodId) {
Object method = authMethods.get(new Integer(methodId));
if (method == null)
return null;
return (Authentication) method;
}
/**
* 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;
}
/**
* Wether to resolve address locally or to let proxy do so.
* <p>
* SOCKS5 protocol allows to send host names rather then IPs in the
* requests, this option controls wether the hostnames should be send to the
* proxy server as names, or should they be resolved locally.
*
* @param doResolve
* Wether to perform resolution locally.
* @return Previous settings.
*/
public boolean resolveAddrLocally(boolean doResolve) {
boolean old = resolveAddrLocally;
resolveAddrLocally = doResolve;
return old;
}
/**
* 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;
}
/**
*
*
*/
@Override
protected void startSession() throws SocksException {
super.startSession();
Authentication auth;
Socket ps = proxySocket; // The name is too long
try {
byte nMethods = (byte) authMethods.size(); // Number of methods
byte[] buf = new byte[2 + nMethods]; // 2 is for VER,NMETHODS
buf[0] = (byte) version;
buf[1] = nMethods; // Number of methods
int i = 2;
Enumeration<Integer> ids = authMethods.keys();
while (ids.hasMoreElements())
buf[i++] = (byte) ids.nextElement().intValue();
out.write(buf);
out.flush();
int versionNumber = in.read();
selectedMethod = in.read();
if (versionNumber < 0 || selectedMethod < 0) {
// EOF condition was reached
endSession();
throw (new SocksException(SOCKS_PROXY_IO_ERROR,
"Connection to proxy lost."));
}
if (versionNumber < version) {
// What should we do??
}
if (selectedMethod == 0xFF) { // No method selected
ps.close();
throw (new SocksException(SOCKS_AUTH_NOT_SUPPORTED));
}
auth = getAuthenticationMethod(selectedMethod);
if (auth == null) {
// This shouldn't happen, unless method was removed by other
// thread, or the server stuffed up
throw (new SocksException(SOCKS_JUST_ERROR,
"Speciefied Authentication not found!"));
}
Object[] in_out = auth.doSocksAuthentication(selectedMethod, ps);
if (in_out == null) {
// Authentication failed by some reason
throw (new SocksException(SOCKS_AUTH_FAILURE));
}
// Most authentication methods are expected to return
// simply the input/output streams associated with
// the socket. However if the auth. method requires
// some kind of encryption/decryption being done on the
// connection it should provide classes to handle I/O.
in = (InputStream) in_out[0];
out = (OutputStream) in_out[1];
if (in_out.length > 2)
udp_encapsulation = (UDPEncapsulation) in_out[2];
} catch (SocksException s_ex) {
throw s_ex;
} catch (UnknownHostException uh_ex) {
throw (new SocksException(SOCKS_PROXY_NO_CONNECT));
} catch (SocketException so_ex) {
throw (new SocksException(SOCKS_PROXY_NO_CONNECT));
} catch (IOException io_ex) {
// System.err.println(io_ex);
throw (new SocksException(SOCKS_PROXY_IO_ERROR, "" + io_ex));
}
}
}