SocksSocketImpl.java revision 0
325N/A/*
325N/A * Copyright 2000-2007 Sun Microsystems, Inc. All Rights Reserved.
325N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
325N/A *
325N/A * This code is free software; you can redistribute it and/or modify it
325N/A * under the terms of the GNU General Public License version 2 only, as
325N/A * published by the Free Software Foundation. Sun designates this
325N/A * particular file as subject to the "Classpath" exception as provided
325N/A * by Sun in the LICENSE file that accompanied this code.
325N/A *
325N/A * This code is distributed in the hope that it will be useful, but WITHOUT
325N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
325N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
325N/A * version 2 for more details (a copy is included in the LICENSE file that
325N/A * accompanied this code).
325N/A *
325N/A * You should have received a copy of the GNU General Public License version
325N/A * 2 along with this work; if not, write to the Free Software Foundation,
325N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
325N/A *
325N/A * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
325N/A * CA 95054 USA or visit www.sun.com if you need additional information or
325N/A * have any questions.
325N/A */
325N/Apackage java.net;
325N/Aimport java.io.IOException;
325N/Aimport java.io.InputStream;
325N/Aimport java.io.OutputStream;
325N/Aimport java.io.BufferedOutputStream;
325N/Aimport java.security.AccessController;
325N/Aimport java.security.PrivilegedExceptionAction;
325N/Aimport java.util.prefs.Preferences;
325N/Aimport sun.net.www.ParseUtil;
325N/A/* import org.ietf.jgss.*; */
325N/A
325N/A/**
325N/A * SOCKS (V4 & V5) TCP socket implementation (RFC 1928).
325N/A * This is a subclass of PlainSocketImpl.
325N/A * Note this class should <b>NOT</b> be public.
325N/A */
325N/A
325N/Aclass SocksSocketImpl extends PlainSocketImpl implements SocksConsts {
325N/A private String server = null;
325N/A private int port = DEFAULT_PORT;
325N/A private InetSocketAddress external_address;
325N/A private boolean useV4 = false;
325N/A private Socket cmdsock = null;
325N/A private InputStream cmdIn = null;
325N/A private OutputStream cmdOut = null;
325N/A
325N/A SocksSocketImpl() {
325N/A // Nothing needed
325N/A }
325N/A
325N/A SocksSocketImpl(String server, int port) {
325N/A this.server = server;
325N/A this.port = (port == -1 ? DEFAULT_PORT : port);
325N/A }
325N/A
325N/A SocksSocketImpl(Proxy proxy) {
325N/A SocketAddress a = proxy.address();
325N/A if (a instanceof InetSocketAddress) {
325N/A InetSocketAddress ad = (InetSocketAddress) a;
325N/A // Use getHostString() to avoid reverse lookups
325N/A server = ad.getHostString();
325N/A port = ad.getPort();
325N/A }
325N/A }
325N/A
325N/A void setV4() {
325N/A useV4 = true;
325N/A }
325N/A
325N/A private synchronized void privilegedConnect(final String host,
325N/A final int port,
325N/A final int timeout)
325N/A throws IOException
325N/A {
325N/A try {
325N/A AccessController.doPrivileged(
325N/A new java.security.PrivilegedExceptionAction() {
325N/A public Object run() throws IOException {
325N/A superConnectServer(host, port, timeout);
325N/A cmdIn = getInputStream();
325N/A cmdOut = getOutputStream();
325N/A return null;
325N/A }
325N/A });
325N/A } catch (java.security.PrivilegedActionException pae) {
325N/A throw (IOException) pae.getException();
325N/A }
325N/A }
325N/A
325N/A private void superConnectServer(String host, int port,
325N/A int timeout) throws IOException {
325N/A super.connect(new InetSocketAddress(host, port), timeout);
325N/A }
325N/A
325N/A private int readSocksReply(InputStream in, byte[] data) throws IOException {
325N/A int len = data.length;
325N/A int received = 0;
325N/A for (int attempts = 0; received < len && attempts < 3; attempts++) {
325N/A int count = in.read(data, received, len - received);
325N/A if (count < 0)
325N/A throw new SocketException("Malformed reply from SOCKS server");
325N/A received += count;
325N/A }
325N/A return received;
325N/A }
325N/A
325N/A /**
325N/A * Provides the authentication machanism required by the proxy.
325N/A */
325N/A private boolean authenticate(byte method, InputStream in,
325N/A BufferedOutputStream out) throws IOException {
325N/A byte[] data = null;
325N/A int i;
325N/A // No Authentication required. We're done then!
325N/A if (method == NO_AUTH)
325N/A return true;
325N/A /**
325N/A * User/Password authentication. Try, in that order :
325N/A * - The application provided Authenticator, if any
325N/A * - The user preferences java.net.socks.username &
325N/A * java.net.socks.password
325N/A * - the user.name & no password (backward compatibility behavior).
325N/A */
325N/A if (method == USER_PASSW) {
325N/A String userName;
325N/A String password = null;
325N/A final InetAddress addr = InetAddress.getByName(server);
325N/A PasswordAuthentication pw = (PasswordAuthentication)
325N/A java.security.AccessController.doPrivileged(
325N/A new java.security.PrivilegedAction() {
325N/A public Object run() {
325N/A return Authenticator.requestPasswordAuthentication(
325N/A server, addr, port, "SOCKS5", "SOCKS authentication", null);
325N/A }
325N/A });
325N/A if (pw != null) {
325N/A userName = pw.getUserName();
325N/A password = new String(pw.getPassword());
325N/A } else {
325N/A final Preferences prefs = Preferences.userRoot().node("/java/net/socks");
325N/A try {
325N/A userName = AccessController.doPrivileged(
325N/A new java.security.PrivilegedExceptionAction<String>() {
325N/A public String run() throws IOException {
325N/A return prefs.get("username", null);
325N/A }
325N/A });
325N/A } catch (java.security.PrivilegedActionException pae) {
325N/A throw (IOException) pae.getException();
325N/A }
325N/A
325N/A if (userName != null) {
325N/A try {
325N/A password = AccessController.doPrivileged(
325N/A new java.security.PrivilegedExceptionAction<String>() {
325N/A public String run() throws IOException {
325N/A return prefs.get("password", null);
325N/A }
325N/A });
325N/A } catch (java.security.PrivilegedActionException pae) {
325N/A throw (IOException) pae.getException();
325N/A }
325N/A } else {
325N/A userName = java.security.AccessController.doPrivileged(
325N/A new sun.security.action.GetPropertyAction("user.name"));
325N/A }
325N/A }
if (userName == null)
return false;
out.write(1);
out.write(userName.length());
try {
out.write(userName.getBytes("ISO-8859-1"));
} catch (java.io.UnsupportedEncodingException uee) {
assert false;
}
if (password != null) {
out.write(password.length());
try {
out.write(password.getBytes("ISO-8859-1"));
} catch (java.io.UnsupportedEncodingException uee) {
assert false;
}
} else
out.write(0);
out.flush();
data = new byte[2];
i = readSocksReply(in, data);
if (i != 2 || data[1] != 0) {
/* RFC 1929 specifies that the connection MUST be closed if
authentication fails */
out.close();
in.close();
return false;
}
/* Authentication succeeded */
return true;
}
/**
* GSSAPI authentication mechanism.
* Unfortunately the RFC seems out of sync with the Reference
* implementation. I'll leave this in for future completion.
*/
// if (method == GSSAPI) {
// try {
// GSSManager manager = GSSManager.getInstance();
// GSSName name = manager.createName("SERVICE:socks@"+server,
// null);
// GSSContext context = manager.createContext(name, null, null,
// GSSContext.DEFAULT_LIFETIME);
// context.requestMutualAuth(true);
// context.requestReplayDet(true);
// context.requestSequenceDet(true);
// context.requestCredDeleg(true);
// byte []inToken = new byte[0];
// while (!context.isEstablished()) {
// byte[] outToken
// = context.initSecContext(inToken, 0, inToken.length);
// // send the output token if generated
// if (outToken != null) {
// out.write(1);
// out.write(1);
// out.writeShort(outToken.length);
// out.write(outToken);
// out.flush();
// data = new byte[2];
// i = readSocksReply(in, data);
// if (i != 2 || data[1] == 0xff) {
// in.close();
// out.close();
// return false;
// }
// i = readSocksReply(in, data);
// int len = 0;
// len = ((int)data[0] & 0xff) << 8;
// len += data[1];
// data = new byte[len];
// i = readSocksReply(in, data);
// if (i == len)
// return true;
// in.close();
// out.close();
// }
// }
// } catch (GSSException e) {
// /* RFC 1961 states that if Context initialisation fails the connection
// MUST be closed */
// e.printStackTrace();
// in.close();
// out.close();
// }
// }
return false;
}
private void connectV4(InputStream in, OutputStream out,
InetSocketAddress endpoint) throws IOException {
if (!(endpoint.getAddress() instanceof Inet4Address)) {
throw new SocketException("SOCKS V4 requires IPv4 only addresses");
}
out.write(PROTO_VERS4);
out.write(CONNECT);
out.write((endpoint.getPort() >> 8) & 0xff);
out.write((endpoint.getPort() >> 0) & 0xff);
out.write(endpoint.getAddress().getAddress());
String userName = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("user.name"));
try {
out.write(userName.getBytes("ISO-8859-1"));
} catch (java.io.UnsupportedEncodingException uee) {
assert false;
}
out.write(0);
out.flush();
byte[] data = new byte[8];
int n = readSocksReply(in, data);
if (n != 8)
throw new SocketException("Reply from SOCKS server has bad length: " + n);
if (data[0] != 0 && data[0] != 4)
throw new SocketException("Reply from SOCKS server has bad version");
SocketException ex = null;
switch (data[1]) {
case 90:
// Success!
external_address = endpoint;
break;
case 91:
ex = new SocketException("SOCKS request rejected");
break;
case 92:
ex = new SocketException("SOCKS server couldn't reach destination");
break;
case 93:
ex = new SocketException("SOCKS authentication failed");
break;
default:
ex = new SocketException("Reply from SOCKS server contains bad status");
break;
}
if (ex != null) {
in.close();
out.close();
throw ex;
}
}
/**
* Connects the Socks Socket to the specified endpoint. It will first
* connect to the SOCKS proxy and negotiate the access. If the proxy
* grants the connections, then the connect is successful and all
* further traffic will go to the "real" endpoint.
*
* @param endpoint the <code>SocketAddress</code> to connect to.
* @param timeout the timeout value in milliseconds
* @throws IOException if the connection can't be established.
* @throws SecurityException if there is a security manager and it
* doesn't allow the connection
* @throws IllegalArgumentException if endpoint is null or a
* SocketAddress subclass not supported by this socket
*/
protected void connect(SocketAddress endpoint, int timeout) throws IOException {
SecurityManager security = System.getSecurityManager();
if (endpoint == null || !(endpoint instanceof InetSocketAddress))
throw new IllegalArgumentException("Unsupported address type");
InetSocketAddress epoint = (InetSocketAddress) endpoint;
if (security != null) {
if (epoint.isUnresolved())
security.checkConnect(epoint.getHostName(),
epoint.getPort());
else
security.checkConnect(epoint.getAddress().getHostAddress(),
epoint.getPort());
}
if (server == null) {
// This is the general case
// server is not null only when the socket was created with a
// specified proxy in which case it does bypass the ProxySelector
ProxySelector sel = (ProxySelector)
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
return ProxySelector.getDefault();
}
});
if (sel == null) {
/*
* No default proxySelector --> direct connection
*/
super.connect(epoint, timeout);
return;
}
URI uri = null;
// Use getHostString() to avoid reverse lookups
String host = epoint.getHostString();
// IPv6 litteral?
if (epoint.getAddress() instanceof Inet6Address &&
(!host.startsWith("[")) && (host.indexOf(":") >= 0)) {
host = "[" + host + "]";
}
try {
uri = new URI("socket://" + ParseUtil.encodePath(host) + ":"+ epoint.getPort());
} catch (URISyntaxException e) {
// This shouldn't happen
assert false : e;
}
Proxy p = null;
IOException savedExc = null;
java.util.Iterator<Proxy> iProxy = null;
iProxy = sel.select(uri).iterator();
if (iProxy == null || !(iProxy.hasNext())) {
super.connect(epoint, timeout);
return;
}
while (iProxy.hasNext()) {
p = iProxy.next();
if (p == null || p == Proxy.NO_PROXY) {
super.connect(epoint, timeout);
return;
}
if (p.type() != Proxy.Type.SOCKS)
throw new SocketException("Unknown proxy type : " + p.type());
if (!(p.address() instanceof InetSocketAddress))
throw new SocketException("Unknow address type for proxy: " + p);
// Use getHostString() to avoid reverse lookups
server = ((InetSocketAddress) p.address()).getHostString();
port = ((InetSocketAddress) p.address()).getPort();
// Connects to the SOCKS server
try {
privilegedConnect(server, port, timeout);
// Worked, let's get outta here
break;
} catch (IOException e) {
// Ooops, let's notify the ProxySelector
sel.connectFailed(uri,p.address(),e);
server = null;
port = -1;
savedExc = e;
// Will continue the while loop and try the next proxy
}
}
/*
* If server is still null at this point, none of the proxy
* worked
*/
if (server == null) {
throw new SocketException("Can't connect to SOCKS proxy:"
+ savedExc.getMessage());
}
} else {
// Connects to the SOCKS server
try {
privilegedConnect(server, port, timeout);
} catch (IOException e) {
throw new SocketException(e.getMessage());
}
}
// cmdIn & cmdOut were intialized during the privilegedConnect() call
BufferedOutputStream out = new BufferedOutputStream(cmdOut, 512);
InputStream in = cmdIn;
if (useV4) {
// SOCKS Protocol version 4 doesn't know how to deal with
// DOMAIN type of addresses (unresolved addresses here)
if (epoint.isUnresolved())
throw new UnknownHostException(epoint.toString());
connectV4(in, out, epoint);
return;
}
// This is SOCKS V5
out.write(PROTO_VERS);
out.write(2);
out.write(NO_AUTH);
out.write(USER_PASSW);
out.flush();
byte[] data = new byte[2];
int i = readSocksReply(in, data);
if (i != 2 || ((int)data[0]) != PROTO_VERS) {
// Maybe it's not a V5 sever after all
// Let's try V4 before we give up
// SOCKS Protocol version 4 doesn't know how to deal with
// DOMAIN type of addresses (unresolved addresses here)
if (epoint.isUnresolved())
throw new UnknownHostException(epoint.toString());
connectV4(in, out, epoint);
return;
}
if (((int)data[1]) == NO_METHODS)
throw new SocketException("SOCKS : No acceptable methods");
if (!authenticate(data[1], in, out)) {
throw new SocketException("SOCKS : authentication failed");
}
out.write(PROTO_VERS);
out.write(CONNECT);
out.write(0);
/* Test for IPV4/IPV6/Unresolved */
if (epoint.isUnresolved()) {
out.write(DOMAIN_NAME);
out.write(epoint.getHostName().length());
try {
out.write(epoint.getHostName().getBytes("ISO-8859-1"));
} catch (java.io.UnsupportedEncodingException uee) {
assert false;
}
out.write((epoint.getPort() >> 8) & 0xff);
out.write((epoint.getPort() >> 0) & 0xff);
} else if (epoint.getAddress() instanceof Inet6Address) {
out.write(IPV6);
out.write(epoint.getAddress().getAddress());
out.write((epoint.getPort() >> 8) & 0xff);
out.write((epoint.getPort() >> 0) & 0xff);
} else {
out.write(IPV4);
out.write(epoint.getAddress().getAddress());
out.write((epoint.getPort() >> 8) & 0xff);
out.write((epoint.getPort() >> 0) & 0xff);
}
out.flush();
data = new byte[4];
i = readSocksReply(in, data);
if (i != 4)
throw new SocketException("Reply from SOCKS server has bad length");
SocketException ex = null;
int nport, len;
byte[] addr;
switch (data[1]) {
case REQUEST_OK:
// success!
switch(data[3]) {
case IPV4:
addr = new byte[4];
i = readSocksReply(in, addr);
if (i != 4)
throw new SocketException("Reply from SOCKS server badly formatted");
data = new byte[2];
i = readSocksReply(in, data);
if (i != 2)
throw new SocketException("Reply from SOCKS server badly formatted");
nport = ((int)data[0] & 0xff) << 8;
nport += ((int)data[1] & 0xff);
break;
case DOMAIN_NAME:
len = data[1];
byte[] host = new byte[len];
i = readSocksReply(in, host);
if (i != len)
throw new SocketException("Reply from SOCKS server badly formatted");
data = new byte[2];
i = readSocksReply(in, data);
if (i != 2)
throw new SocketException("Reply from SOCKS server badly formatted");
nport = ((int)data[0] & 0xff) << 8;
nport += ((int)data[1] & 0xff);
break;
case IPV6:
len = data[1];
addr = new byte[len];
i = readSocksReply(in, addr);
if (i != len)
throw new SocketException("Reply from SOCKS server badly formatted");
data = new byte[2];
i = readSocksReply(in, data);
if (i != 2)
throw new SocketException("Reply from SOCKS server badly formatted");
nport = ((int)data[0] & 0xff) << 8;
nport += ((int)data[1] & 0xff);
break;
default:
ex = new SocketException("Reply from SOCKS server contains wrong code");
break;
}
break;
case GENERAL_FAILURE:
ex = new SocketException("SOCKS server general failure");
break;
case NOT_ALLOWED:
ex = new SocketException("SOCKS: Connection not allowed by ruleset");
break;
case NET_UNREACHABLE:
ex = new SocketException("SOCKS: Network unreachable");
break;
case HOST_UNREACHABLE:
ex = new SocketException("SOCKS: Host unreachable");
break;
case CONN_REFUSED:
ex = new SocketException("SOCKS: Connection refused");
break;
case TTL_EXPIRED:
ex = new SocketException("SOCKS: TTL expired");
break;
case CMD_NOT_SUPPORTED:
ex = new SocketException("SOCKS: Command not supported");
break;
case ADDR_TYPE_NOT_SUP:
ex = new SocketException("SOCKS: address type not supported");
break;
}
if (ex != null) {
in.close();
out.close();
throw ex;
}
external_address = epoint;
}
private void bindV4(InputStream in, OutputStream out,
InetAddress baddr,
int lport) throws IOException {
if (!(baddr instanceof Inet4Address)) {
throw new SocketException("SOCKS V4 requires IPv4 only addresses");
}
super.bind(baddr, lport);
byte[] addr1 = baddr.getAddress();
/* Test for AnyLocal */
InetAddress naddr = baddr;
if (naddr.isAnyLocalAddress()) {
naddr = cmdsock.getLocalAddress();
addr1 = naddr.getAddress();
}
out.write(PROTO_VERS4);
out.write(BIND);
out.write((super.getLocalPort() >> 8) & 0xff);
out.write((super.getLocalPort() >> 0) & 0xff);
out.write(addr1);
String userName = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("user.name"));
try {
out.write(userName.getBytes("ISO-8859-1"));
} catch (java.io.UnsupportedEncodingException uee) {
assert false;
}
out.write(0);
out.flush();
byte[] data = new byte[8];
int n = readSocksReply(in, data);
if (n != 8)
throw new SocketException("Reply from SOCKS server has bad length: " + n);
if (data[0] != 0 && data[0] != 4)
throw new SocketException("Reply from SOCKS server has bad version");
SocketException ex = null;
switch (data[1]) {
case 90:
// Success!
external_address = new InetSocketAddress(baddr, lport);
break;
case 91:
ex = new SocketException("SOCKS request rejected");
break;
case 92:
ex = new SocketException("SOCKS server couldn't reach destination");
break;
case 93:
ex = new SocketException("SOCKS authentication failed");
break;
default:
ex = new SocketException("Reply from SOCKS server contains bad status");
break;
}
if (ex != null) {
in.close();
out.close();
throw ex;
}
}
/**
* Sends the Bind request to the SOCKS proxy. In the SOCKS protocol, bind
* means "accept incoming connection from", so the SocketAddress is the
* the one of the host we do accept connection from.
*
* @param addr the Socket address of the remote host.
* @exception IOException if an I/O error occurs when binding this socket.
*/
protected synchronized void socksBind(InetSocketAddress saddr) throws IOException {
if (socket != null) {
// this is a client socket, not a server socket, don't
// call the SOCKS proxy for a bind!
return;
}
// Connects to the SOCKS server
if (server == null) {
// This is the general case
// server is not null only when the socket was created with a
// specified proxy in which case it does bypass the ProxySelector
ProxySelector sel = (ProxySelector)
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
return ProxySelector.getDefault();
}
});
if (sel == null) {
/*
* No default proxySelector --> direct connection
*/
return;
}
URI uri = null;
// Use getHostString() to avoid reverse lookups
String host = saddr.getHostString();
// IPv6 litteral?
if (saddr.getAddress() instanceof Inet6Address &&
(!host.startsWith("[")) && (host.indexOf(":") >= 0)) {
host = "[" + host + "]";
}
try {
uri = new URI("serversocket://" + ParseUtil.encodePath(host) + ":"+ saddr.getPort());
} catch (URISyntaxException e) {
// This shouldn't happen
assert false : e;
}
Proxy p = null;
Exception savedExc = null;
java.util.Iterator<Proxy> iProxy = null;
iProxy = sel.select(uri).iterator();
if (iProxy == null || !(iProxy.hasNext())) {
return;
}
while (iProxy.hasNext()) {
p = iProxy.next();
if (p == null || p == Proxy.NO_PROXY) {
return;
}
if (p.type() != Proxy.Type.SOCKS)
throw new SocketException("Unknown proxy type : " + p.type());
if (!(p.address() instanceof InetSocketAddress))
throw new SocketException("Unknow address type for proxy: " + p);
// Use getHostString() to avoid reverse lookups
server = ((InetSocketAddress) p.address()).getHostString();
port = ((InetSocketAddress) p.address()).getPort();
// Connects to the SOCKS server
try {
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
cmdsock = new Socket(new PlainSocketImpl());
cmdsock.connect(new InetSocketAddress(server, port));
cmdIn = cmdsock.getInputStream();
cmdOut = cmdsock.getOutputStream();
return null;
}
});
} catch (Exception e) {
// Ooops, let's notify the ProxySelector
sel.connectFailed(uri,p.address(),new SocketException(e.getMessage()));
server = null;
port = -1;
cmdsock = null;
savedExc = e;
// Will continue the while loop and try the next proxy
}
}
/*
* If server is still null at this point, none of the proxy
* worked
*/
if (server == null || cmdsock == null) {
throw new SocketException("Can't connect to SOCKS proxy:"
+ savedExc.getMessage());
}
} else {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
cmdsock = new Socket(new PlainSocketImpl());
cmdsock.connect(new InetSocketAddress(server, port));
cmdIn = cmdsock.getInputStream();
cmdOut = cmdsock.getOutputStream();
return null;
}
});
} catch (Exception e) {
throw new SocketException(e.getMessage());
}
}
BufferedOutputStream out = new BufferedOutputStream(cmdOut, 512);
InputStream in = cmdIn;
if (useV4) {
bindV4(in, out, saddr.getAddress(), saddr.getPort());
return;
}
out.write(PROTO_VERS);
out.write(2);
out.write(NO_AUTH);
out.write(USER_PASSW);
out.flush();
byte[] data = new byte[2];
int i = readSocksReply(in, data);
if (i != 2 || ((int)data[0]) != PROTO_VERS) {
// Maybe it's not a V5 sever after all
// Let's try V4 before we give up
bindV4(in, out, saddr.getAddress(), saddr.getPort());
return;
}
if (((int)data[1]) == NO_METHODS)
throw new SocketException("SOCKS : No acceptable methods");
if (!authenticate(data[1], in, out)) {
throw new SocketException("SOCKS : authentication failed");
}
// We're OK. Let's issue the BIND command.
out.write(PROTO_VERS);
out.write(BIND);
out.write(0);
int lport = saddr.getPort();
if (saddr.isUnresolved()) {
out.write(DOMAIN_NAME);
out.write(saddr.getHostName().length());
try {
out.write(saddr.getHostName().getBytes("ISO-8859-1"));
} catch (java.io.UnsupportedEncodingException uee) {
assert false;
}
out.write((lport >> 8) & 0xff);
out.write((lport >> 0) & 0xff);
} else if (saddr.getAddress() instanceof Inet4Address) {
byte[] addr1 = saddr.getAddress().getAddress();
out.write(IPV4);
out.write(addr1);
out.write((lport >> 8) & 0xff);
out.write((lport >> 0) & 0xff);
out.flush();
} else if (saddr.getAddress() instanceof Inet6Address) {
byte[] addr1 = saddr.getAddress().getAddress();
out.write(IPV6);
out.write(addr1);
out.write((lport >> 8) & 0xff);
out.write((lport >> 0) & 0xff);
out.flush();
} else {
cmdsock.close();
throw new SocketException("unsupported address type : " + saddr);
}
data = new byte[4];
i = readSocksReply(in, data);
SocketException ex = null;
int len, nport;
byte[] addr;
switch (data[1]) {
case REQUEST_OK:
// success!
InetSocketAddress real_end = null;
switch(data[3]) {
case IPV4:
addr = new byte[4];
i = readSocksReply(in, addr);
if (i != 4)
throw new SocketException("Reply from SOCKS server badly formatted");
data = new byte[2];
i = readSocksReply(in, data);
if (i != 2)
throw new SocketException("Reply from SOCKS server badly formatted");
nport = ((int)data[0] & 0xff) << 8;
nport += ((int)data[1] & 0xff);
external_address =
new InetSocketAddress(new Inet4Address("", addr) , nport);
break;
case DOMAIN_NAME:
len = data[1];
byte[] host = new byte[len];
i = readSocksReply(in, host);
if (i != len)
throw new SocketException("Reply from SOCKS server badly formatted");
data = new byte[2];
i = readSocksReply(in, data);
if (i != 2)
throw new SocketException("Reply from SOCKS server badly formatted");
nport = ((int)data[0] & 0xff) << 8;
nport += ((int)data[1] & 0xff);
external_address = new InetSocketAddress(new String(host), nport);
break;
case IPV6:
len = data[1];
addr = new byte[len];
i = readSocksReply(in, addr);
if (i != len)
throw new SocketException("Reply from SOCKS server badly formatted");
data = new byte[2];
i = readSocksReply(in, data);
if (i != 2)
throw new SocketException("Reply from SOCKS server badly formatted");
nport = ((int)data[0] & 0xff) << 8;
nport += ((int)data[1] & 0xff);
external_address =
new InetSocketAddress(new Inet6Address("", addr), nport);
break;
}
break;
case GENERAL_FAILURE:
ex = new SocketException("SOCKS server general failure");
break;
case NOT_ALLOWED:
ex = new SocketException("SOCKS: Bind not allowed by ruleset");
break;
case NET_UNREACHABLE:
ex = new SocketException("SOCKS: Network unreachable");
break;
case HOST_UNREACHABLE:
ex = new SocketException("SOCKS: Host unreachable");
break;
case CONN_REFUSED:
ex = new SocketException("SOCKS: Connection refused");
break;
case TTL_EXPIRED:
ex = new SocketException("SOCKS: TTL expired");
break;
case CMD_NOT_SUPPORTED:
ex = new SocketException("SOCKS: Command not supported");
break;
case ADDR_TYPE_NOT_SUP:
ex = new SocketException("SOCKS: address type not supported");
break;
}
if (ex != null) {
in.close();
out.close();
cmdsock.close();
cmdsock = null;
throw ex;
}
cmdIn = in;
cmdOut = out;
}
/**
* Accepts a connection from a specific host.
*
* @param s the accepted connection.
* @param saddr the socket address of the host we do accept
* connection from
* @exception IOException if an I/O error occurs when accepting the
* connection.
*/
protected void acceptFrom(SocketImpl s, InetSocketAddress saddr) throws IOException {
if (cmdsock == null) {
// Not a Socks ServerSocket.
return;
}
InputStream in = cmdIn;
// Sends the "SOCKS BIND" request.
socksBind(saddr);
in.read();
int i = in.read();
in.read();
SocketException ex = null;
int nport;
byte[] addr;
InetSocketAddress real_end = null;
switch (i) {
case REQUEST_OK:
// success!
i = in.read();
switch(i) {
case IPV4:
addr = new byte[4];
readSocksReply(in, addr);
nport = in.read() << 8;
nport += in.read();
real_end =
new InetSocketAddress(new Inet4Address("", addr) , nport);
break;
case DOMAIN_NAME:
int len = in.read();
addr = new byte[len];
readSocksReply(in, addr);
nport = in.read() << 8;
nport += in.read();
real_end = new InetSocketAddress(new String(addr), nport);
break;
case IPV6:
addr = new byte[16];
readSocksReply(in, addr);
nport = in.read() << 8;
nport += in.read();
real_end =
new InetSocketAddress(new Inet6Address("", addr), nport);
break;
}
break;
case GENERAL_FAILURE:
ex = new SocketException("SOCKS server general failure");
break;
case NOT_ALLOWED:
ex = new SocketException("SOCKS: Accept not allowed by ruleset");
break;
case NET_UNREACHABLE:
ex = new SocketException("SOCKS: Network unreachable");
break;
case HOST_UNREACHABLE:
ex = new SocketException("SOCKS: Host unreachable");
break;
case CONN_REFUSED:
ex = new SocketException("SOCKS: Connection refused");
break;
case TTL_EXPIRED:
ex = new SocketException("SOCKS: TTL expired");
break;
case CMD_NOT_SUPPORTED:
ex = new SocketException("SOCKS: Command not supported");
break;
case ADDR_TYPE_NOT_SUP:
ex = new SocketException("SOCKS: address type not supported");
break;
}
if (ex != null) {
cmdIn.close();
cmdOut.close();
cmdsock.close();
cmdsock = null;
throw ex;
}
/**
* This is where we have to do some fancy stuff.
* The datastream from the socket "accepted" by the proxy will
* come through the cmdSocket. So we have to swap the socketImpls
*/
if (s instanceof SocksSocketImpl) {
((SocksSocketImpl)s).external_address = real_end;
}
if (s instanceof PlainSocketImpl) {
PlainSocketImpl psi = (PlainSocketImpl) s;
psi.setInputStream((SocketInputStream) in);
psi.setFileDescriptor(cmdsock.getImpl().getFileDescriptor());
psi.setAddress(cmdsock.getImpl().getInetAddress());
psi.setPort(cmdsock.getImpl().getPort());
psi.setLocalPort(cmdsock.getImpl().getLocalPort());
} else {
s.fd = cmdsock.getImpl().fd;
s.address = cmdsock.getImpl().address;
s.port = cmdsock.getImpl().port;
s.localport = cmdsock.getImpl().localport;
}
// Need to do that so that the socket won't be closed
// when the ServerSocket is closed by the user.
// It kinds of detaches the Socket because it is now
// used elsewhere.
cmdsock = null;
}
/**
* Returns the value of this socket's <code>address</code> field.
*
* @return the value of this socket's <code>address</code> field.
* @see java.net.SocketImpl#address
*/
protected InetAddress getInetAddress() {
if (external_address != null)
return external_address.getAddress();
else
return super.getInetAddress();
}
/**
* Returns the value of this socket's <code>port</code> field.
*
* @return the value of this socket's <code>port</code> field.
* @see java.net.SocketImpl#port
*/
protected int getPort() {
if (external_address != null)
return external_address.getPort();
else
return super.getPort();
}
protected int getLocalPort() {
if (socket != null)
return super.getLocalPort();
if (external_address != null)
return external_address.getPort();
else
return super.getLocalPort();
}
protected void close() throws IOException {
if (cmdsock != null)
cmdsock.close();
cmdsock = null;
super.close();
}
}