jdk/src/share/classes/java/net/SocksSocketImpl.java
changeset 2 90ce3da70b43
child 51 6fe31bc95bbc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/java/net/SocksSocketImpl.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1055 @@
+/*
+ * Copyright 2000-2007 Sun Microsystems, Inc.  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.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.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.PrivilegedExceptionAction;
+import java.util.prefs.Preferences;
+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 port = DEFAULT_PORT;
+    private InetSocketAddress external_address;
+    private boolean useV4 = false;
+    private Socket cmdsock = null;
+    private InputStream cmdIn = null;
+    private OutputStream cmdOut = null;
+
+    SocksSocketImpl() {
+        // Nothing needed
+    }
+
+    SocksSocketImpl(String server, int port) {
+        this.server = server;
+        this.port = (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();
+            port = ad.getPort();
+        }
+    }
+
+    void setV4() {
+        useV4 = true;
+    }
+
+    private synchronized void privilegedConnect(final String host,
+                                              final int port,
+                                              final int timeout)
+         throws IOException
+    {
+        try {
+            AccessController.doPrivileged(
+                  new java.security.PrivilegedExceptionAction() {
+                          public Object 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 int readSocksReply(InputStream in, byte[] data) throws IOException {
+        int len = data.length;
+        int received = 0;
+        for (int attempts = 0; received < len && attempts < 3; attempts++) {
+            int count = in.read(data, received, len - received);
+            if (count < 0)
+                throw new SocketException("Malformed reply from SOCKS server");
+            received += count;
+        }
+        return received;
+    }
+
+    /**
+     * Provides the authentication machanism required by the proxy.
+     */
+    private boolean authenticate(byte method, InputStream in,
+                                 BufferedOutputStream out) throws IOException {
+        byte[] data = null;
+        int i;
+        // 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 preferences java.net.socks.username &
+         *   java.net.socks.password
+         * - 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 = (PasswordAuthentication)
+                java.security.AccessController.doPrivileged(
+                    new java.security.PrivilegedAction() {
+                            public Object run() {
+                                return Authenticator.requestPasswordAuthentication(
+                                       server, addr, port, "SOCKS5", "SOCKS authentication", null);
+                            }
+                        });
+            if (pw != null) {
+                userName = pw.getUserName();
+                password = new String(pw.getPassword());
+            } else {
+                final Preferences prefs = Preferences.userRoot().node("/java/net/socks");
+                try {
+                    userName = AccessController.doPrivileged(
+                        new java.security.PrivilegedExceptionAction<String>() {
+                            public String run() throws IOException {
+                                return prefs.get("username", null);
+                            }
+                        });
+                } catch (java.security.PrivilegedActionException pae) {
+                    throw (IOException) pae.getException();
+                }
+
+                if (userName != null) {
+                    try {
+                        password = AccessController.doPrivileged(
+                            new java.security.PrivilegedExceptionAction<String>() {
+                                public String run() throws IOException {
+                                    return prefs.get("password", null);
+                                }
+                            });
+                    } catch (java.security.PrivilegedActionException pae) {
+                        throw (IOException) pae.getException();
+                    }
+                } else {
+                    userName = java.security.AccessController.doPrivileged(
+                        new sun.security.action.GetPropertyAction("user.name"));
+                }
+            }
+            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();
+    }
+
+}