| /* |
| * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package java.net; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.BufferedOutputStream; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.security.PrivilegedExceptionAction; |
| |
| import jdk.internal.util.StaticProperty; |
| import sun.net.SocksProxy; |
| import sun.net.spi.DefaultProxySelector; |
| import sun.net.www.ParseUtil; |
| /* import org.ietf.jgss.*; */ |
| |
| /** |
| * SOCKS (V4 & V5) TCP socket implementation (RFC 1928). |
| * This is a subclass of PlainSocketImpl. |
| * Note this class should <b>NOT</b> be public. |
| */ |
| |
| class SocksSocketImpl extends PlainSocketImpl implements SocksConsts { |
| private String server = null; |
| private int serverPort = DEFAULT_PORT; |
| private InetSocketAddress external_address; |
| private boolean useV4 = false; |
| private Socket cmdsock = null; |
| private InputStream cmdIn = null; |
| private OutputStream cmdOut = null; |
| /* true if the Proxy has been set programmatically */ |
| private boolean applicationSetProxy; /* false */ |
| |
| |
| SocksSocketImpl() { |
| // Nothing needed |
| } |
| |
| SocksSocketImpl(String server, int port) { |
| this.server = server; |
| this.serverPort = (port == -1 ? DEFAULT_PORT : port); |
| } |
| |
| SocksSocketImpl(Proxy proxy) { |
| SocketAddress a = proxy.address(); |
| if (a instanceof InetSocketAddress) { |
| InetSocketAddress ad = (InetSocketAddress) a; |
| // Use getHostString() to avoid reverse lookups |
| server = ad.getHostString(); |
| serverPort = ad.getPort(); |
| } |
| useV4 = useV4(proxy); |
| } |
| |
| void setV4() { |
| useV4 = true; |
| } |
| |
| private static boolean useV4(Proxy proxy) { |
| if (proxy instanceof SocksProxy |
| && ((SocksProxy)proxy).protocolVersion() == 4) { |
| return true; |
| } |
| return DefaultProxySelector.socksProxyVersion() == 4; |
| } |
| |
| private synchronized void privilegedConnect(final String host, |
| final int port, |
| final int timeout) |
| throws IOException |
| { |
| try { |
| AccessController.doPrivileged( |
| new java.security.PrivilegedExceptionAction<>() { |
| public Void run() throws IOException { |
| superConnectServer(host, port, timeout); |
| cmdIn = getInputStream(); |
| cmdOut = getOutputStream(); |
| return null; |
| } |
| }); |
| } catch (java.security.PrivilegedActionException pae) { |
| throw (IOException) pae.getException(); |
| } |
| } |
| |
| private void superConnectServer(String host, int port, |
| int timeout) throws IOException { |
| super.connect(new InetSocketAddress(host, port), timeout); |
| } |
| |
| private static int remainingMillis(long deadlineMillis) throws IOException { |
| if (deadlineMillis == 0L) |
| return 0; |
| |
| final long remaining = deadlineMillis - System.currentTimeMillis(); |
| if (remaining > 0) |
| return (int) remaining; |
| |
| throw new SocketTimeoutException(); |
| } |
| |
| private int readSocksReply(InputStream in, byte[] data) throws IOException { |
| return readSocksReply(in, data, 0L); |
| } |
| |
| private int readSocksReply(InputStream in, byte[] data, long deadlineMillis) throws IOException { |
| int len = data.length; |
| int received = 0; |
| while (received < len) { |
| int count; |
| try { |
| count = ((SocketInputStream)in).read(data, received, len - received, remainingMillis(deadlineMillis)); |
| } catch (SocketTimeoutException e) { |
| throw new SocketTimeoutException("Connect timed out"); |
| } |
| if (count < 0) |
| throw new SocketException("Malformed reply from SOCKS server"); |
| received += count; |
| } |
| return received; |
| } |
| |
| /** |
| * Provides the authentication mechanism required by the proxy. |
| */ |
| private boolean authenticate(byte method, InputStream in, |
| BufferedOutputStream out) throws IOException { |
| return authenticate(method, in, out, 0L); |
| } |
| |
| private boolean authenticate(byte method, InputStream in, |
| BufferedOutputStream out, |
| long deadlineMillis) throws IOException { |
| // No Authentication required. We're done then! |
| if (method == NO_AUTH) |
| return true; |
| /* |
| * User/Password authentication. Try, in that order : |
| * - The application provided Authenticator, if any |
| * - the user.name & no password (backward compatibility behavior). |
| */ |
| if (method == USER_PASSW) { |
| String userName; |
| String password = null; |
| final InetAddress addr = InetAddress.getByName(server); |
| PasswordAuthentication pw = |
| java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<>() { |
| public PasswordAuthentication run() { |
| return Authenticator.requestPasswordAuthentication( |
| server, addr, serverPort, "SOCKS5", "SOCKS authentication", null); |
| } |
| }); |
| if (pw != null) { |
| userName = pw.getUserName(); |
| password = new String(pw.getPassword()); |
| } else { |
| userName = StaticProperty.userName(); |
| } |
| 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(); |
| byte[] data = new byte[2]; |
| int i = readSocksReply(in, data, deadlineMillis); |
| 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, deadlineMillis); |
| // if (i != 2 || data[1] == 0xff) { |
| // in.close(); |
| // out.close(); |
| // return false; |
| // } |
| // i = readSocksReply(in, data, deadlineMillis); |
| // int len = 0; |
| // len = ((int)data[0] & 0xff) << 8; |
| // len += data[1]; |
| // data = new byte[len]; |
| // i = readSocksReply(in, data, deadlineMillis); |
| // 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, |
| long deadlineMillis) 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 = getUserName(); |
| 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, deadlineMillis); |
| 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} 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 |
| */ |
| @Override |
| protected void connect(SocketAddress endpoint, int timeout) throws IOException { |
| final long deadlineMillis; |
| |
| if (timeout == 0) { |
| deadlineMillis = 0L; |
| } else { |
| long finish = System.currentTimeMillis() + timeout; |
| deadlineMillis = finish < 0 ? Long.MAX_VALUE : finish; |
| } |
| |
| 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 = java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<>() { |
| public ProxySelector run() { |
| return ProxySelector.getDefault(); |
| } |
| }); |
| if (sel == null) { |
| /* |
| * No default proxySelector --> direct connection |
| */ |
| super.connect(epoint, remainingMillis(deadlineMillis)); |
| return; |
| } |
| URI uri; |
| // Use getHostString() to avoid reverse lookups |
| String host = epoint.getHostString(); |
| // IPv6 literal? |
| 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; |
| uri = null; |
| } |
| 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, remainingMillis(deadlineMillis)); |
| return; |
| } |
| while (iProxy.hasNext()) { |
| p = iProxy.next(); |
| if (p == null || p.type() != Proxy.Type.SOCKS) { |
| super.connect(epoint, remainingMillis(deadlineMillis)); |
| return; |
| } |
| |
| if (!(p.address() instanceof InetSocketAddress)) |
| throw new SocketException("Unknown address type for proxy: " + p); |
| // Use getHostString() to avoid reverse lookups |
| server = ((InetSocketAddress) p.address()).getHostString(); |
| serverPort = ((InetSocketAddress) p.address()).getPort(); |
| useV4 = useV4(p); |
| |
| // Connects to the SOCKS server |
| try { |
| privilegedConnect(server, serverPort, remainingMillis(deadlineMillis)); |
| // Worked, let's get outta here |
| break; |
| } catch (IOException e) { |
| // Ooops, let's notify the ProxySelector |
| sel.connectFailed(uri,p.address(),e); |
| server = null; |
| serverPort = -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, serverPort, remainingMillis(deadlineMillis)); |
| } catch (IOException e) { |
| throw new SocketException(e.getMessage()); |
| } |
| } |
| |
| // cmdIn & cmdOut were initialized 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, deadlineMillis); |
| 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, deadlineMillis); |
| 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, deadlineMillis); |
| return; |
| } |
| if (((int)data[1]) == NO_METHODS) |
| throw new SocketException("SOCKS : No acceptable methods"); |
| if (!authenticate(data[1], in, out, deadlineMillis)) { |
| 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, deadlineMillis); |
| if (i != 4) |
| throw new SocketException("Reply from SOCKS server has bad length"); |
| SocketException ex = null; |
| int len; |
| byte[] addr; |
| switch (data[1]) { |
| case REQUEST_OK: |
| // success! |
| switch(data[3]) { |
| case IPV4: |
| addr = new byte[4]; |
| i = readSocksReply(in, addr, deadlineMillis); |
| if (i != 4) |
| throw new SocketException("Reply from SOCKS server badly formatted"); |
| data = new byte[2]; |
| i = readSocksReply(in, data, deadlineMillis); |
| if (i != 2) |
| throw new SocketException("Reply from SOCKS server badly formatted"); |
| break; |
| case DOMAIN_NAME: |
| byte[] lenByte = new byte[1]; |
| i = readSocksReply(in, lenByte, deadlineMillis); |
| if (i != 1) |
| throw new SocketException("Reply from SOCKS server badly formatted"); |
| len = lenByte[0] & 0xFF; |
| byte[] host = new byte[len]; |
| i = readSocksReply(in, host, deadlineMillis); |
| if (i != len) |
| throw new SocketException("Reply from SOCKS server badly formatted"); |
| data = new byte[2]; |
| i = readSocksReply(in, data, deadlineMillis); |
| if (i != 2) |
| throw new SocketException("Reply from SOCKS server badly formatted"); |
| break; |
| case IPV6: |
| len = 16; |
| addr = new byte[len]; |
| i = readSocksReply(in, addr, deadlineMillis); |
| if (i != len) |
| throw new SocketException("Reply from SOCKS server badly formatted"); |
| data = new byte[2]; |
| i = readSocksReply(in, data, deadlineMillis); |
| if (i != 2) |
| throw new SocketException("Reply from SOCKS server badly formatted"); |
| 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 = AccessController.doPrivileged( |
| new PrivilegedAction<>() { |
| public InetAddress run() { |
| return 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 = getUserName(); |
| 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 one of the host we do accept connection from. |
| * |
| * @param saddr 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 = java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<>() { |
| public ProxySelector run() { |
| return ProxySelector.getDefault(); |
| } |
| }); |
| if (sel == null) { |
| /* |
| * No default proxySelector --> direct connection |
| */ |
| return; |
| } |
| URI uri; |
| // Use getHostString() to avoid reverse lookups |
| String host = saddr.getHostString(); |
| // IPv6 literal? |
| 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; |
| uri = null; |
| } |
| 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.type() != Proxy.Type.SOCKS) { |
| return; |
| } |
| |
| if (!(p.address() instanceof InetSocketAddress)) |
| throw new SocketException("Unknown address type for proxy: " + p); |
| // Use getHostString() to avoid reverse lookups |
| server = ((InetSocketAddress) p.address()).getHostString(); |
| serverPort = ((InetSocketAddress) p.address()).getPort(); |
| useV4 = useV4(p); |
| |
| // Connects to the SOCKS server |
| try { |
| AccessController.doPrivileged( |
| new PrivilegedExceptionAction<>() { |
| public Void run() throws Exception { |
| cmdsock = new Socket(new PlainSocketImpl()); |
| cmdsock.connect(new InetSocketAddress(server, serverPort)); |
| 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; |
| serverPort = -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 Void run() throws Exception { |
| cmdsock = new Socket(new PlainSocketImpl()); |
| cmdsock.connect(new InetSocketAddress(server, serverPort)); |
| 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! |
| 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} field. |
| * |
| * @return the value of this socket's {@code address} field. |
| * @see java.net.SocketImpl#address |
| */ |
| @Override |
| protected InetAddress getInetAddress() { |
| if (external_address != null) |
| return external_address.getAddress(); |
| else |
| return super.getInetAddress(); |
| } |
| |
| /** |
| * Returns the value of this socket's {@code port} field. |
| * |
| * @return the value of this socket's {@code port} field. |
| * @see java.net.SocketImpl#port |
| */ |
| @Override |
| protected int getPort() { |
| if (external_address != null) |
| return external_address.getPort(); |
| else |
| return super.getPort(); |
| } |
| |
| @Override |
| protected int getLocalPort() { |
| if (socket != null) |
| return super.getLocalPort(); |
| if (external_address != null) |
| return external_address.getPort(); |
| else |
| return super.getLocalPort(); |
| } |
| |
| @Override |
| protected void close() throws IOException { |
| if (cmdsock != null) |
| cmdsock.close(); |
| cmdsock = null; |
| super.close(); |
| } |
| |
| private String getUserName() { |
| String userName = ""; |
| if (applicationSetProxy) { |
| try { |
| userName = System.getProperty("user.name"); |
| } catch (SecurityException se) { /* swallow Exception */ } |
| } else { |
| userName = StaticProperty.userName(); |
| } |
| return userName; |
| } |
| } |