SocksSocketImpl.java revision 740b8c08984cd8b93da426c6c7fb7ca75739af9a
1/*
2 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package java.net;
26import java.io.IOException;
27import java.io.InputStream;
28import java.io.OutputStream;
29import java.io.BufferedOutputStream;
30import java.security.AccessController;
31import java.security.PrivilegedAction;
32import java.security.PrivilegedExceptionAction;
33import sun.net.SocksProxy;
34import sun.net.www.ParseUtil;
35/* import org.ietf.jgss.*; */
36
37/**
38 * SOCKS (V4 & V5) TCP socket implementation (RFC 1928).
39 * This is a subclass of PlainSocketImpl.
40 * Note this class should <b>NOT</b> be public.
41 */
42
43class SocksSocketImpl extends PlainSocketImpl implements SocksConsts {
44    private String server = null;
45    private int serverPort = DEFAULT_PORT;
46    private InetSocketAddress external_address;
47    private boolean useV4 = false;
48    private Socket cmdsock = null;
49    private InputStream cmdIn = null;
50    private OutputStream cmdOut = null;
51    /* true if the Proxy has been set programatically */
52    private boolean applicationSetProxy;  /* false */
53
54
55    SocksSocketImpl() {
56        // Nothing needed
57    }
58
59    SocksSocketImpl(String server, int port) {
60        this.server = server;
61        this.serverPort = (port == -1 ? DEFAULT_PORT : port);
62    }
63
64    SocksSocketImpl(Proxy proxy) {
65        SocketAddress a = proxy.address();
66        if (a instanceof InetSocketAddress) {
67            InetSocketAddress ad = (InetSocketAddress) a;
68            // Use getHostString() to avoid reverse lookups
69            server = ad.getHostString();
70            serverPort = ad.getPort();
71        }
72    }
73
74    void setV4() {
75        useV4 = true;
76    }
77
78    private synchronized void privilegedConnect(final String host,
79                                              final int port,
80                                              final int timeout)
81         throws IOException
82    {
83        try {
84            AccessController.doPrivileged(
85                new java.security.PrivilegedExceptionAction<Void>() {
86                    public Void run() throws IOException {
87                              superConnectServer(host, port, timeout);
88                              cmdIn = getInputStream();
89                              cmdOut = getOutputStream();
90                              return null;
91                          }
92                      });
93        } catch (java.security.PrivilegedActionException pae) {
94            throw (IOException) pae.getException();
95        }
96    }
97
98    private void superConnectServer(String host, int port,
99                                    int timeout) throws IOException {
100        super.connect(new InetSocketAddress(host, port), timeout);
101    }
102
103    private static int remainingMillis(long deadlineMillis) throws IOException {
104        if (deadlineMillis == 0L)
105            return 0;
106
107        final long remaining = deadlineMillis - System.currentTimeMillis();
108        if (remaining > 0)
109            return (int) remaining;
110
111        throw new SocketTimeoutException();
112    }
113
114    private int readSocksReply(InputStream in, byte[] data) throws IOException {
115        return readSocksReply(in, data, 0L);
116    }
117
118    private int readSocksReply(InputStream in, byte[] data, long deadlineMillis) throws IOException {
119        int len = data.length;
120        int received = 0;
121        for (int attempts = 0; received < len && attempts < 3; attempts++) {
122            int count;
123            try {
124                count = ((SocketInputStream)in).read(data, received, len - received, remainingMillis(deadlineMillis));
125            } catch (SocketTimeoutException e) {
126                throw new SocketTimeoutException("Connect timed out");
127            }
128            if (count < 0)
129                throw new SocketException("Malformed reply from SOCKS server");
130            received += count;
131        }
132        return received;
133    }
134
135    /**
136     * Provides the authentication machanism required by the proxy.
137     */
138    private boolean authenticate(byte method, InputStream in,
139                                 BufferedOutputStream out) throws IOException {
140        return authenticate(method, in, out, 0L);
141    }
142
143    private boolean authenticate(byte method, InputStream in,
144                                 BufferedOutputStream out,
145                                 long deadlineMillis) throws IOException {
146        // No Authentication required. We're done then!
147        if (method == NO_AUTH)
148            return true;
149        /**
150         * User/Password authentication. Try, in that order :
151         * - The application provided Authenticator, if any
152         * - the user.name & no password (backward compatibility behavior).
153         */
154        if (method == USER_PASSW) {
155            String userName;
156            String password = null;
157            final InetAddress addr = InetAddress.getByName(server);
158            PasswordAuthentication pw =
159                java.security.AccessController.doPrivileged(
160                    new java.security.PrivilegedAction<PasswordAuthentication>() {
161                        public PasswordAuthentication run() {
162                                return Authenticator.requestPasswordAuthentication(
163                                       server, addr, serverPort, "SOCKS5", "SOCKS authentication", null);
164                            }
165                        });
166            if (pw != null) {
167                userName = pw.getUserName();
168                password = new String(pw.getPassword());
169            } else {
170                userName = java.security.AccessController.doPrivileged(
171                        new sun.security.action.GetPropertyAction("user.name"));
172            }
173            if (userName == null)
174                return false;
175            out.write(1);
176            out.write(userName.length());
177            try {
178                out.write(userName.getBytes("ISO-8859-1"));
179            } catch (java.io.UnsupportedEncodingException uee) {
180                assert false;
181            }
182            if (password != null) {
183                out.write(password.length());
184                try {
185                    out.write(password.getBytes("ISO-8859-1"));
186                } catch (java.io.UnsupportedEncodingException uee) {
187                    assert false;
188                }
189            } else
190                out.write(0);
191            out.flush();
192            byte[] data = new byte[2];
193            int i = readSocksReply(in, data, deadlineMillis);
194            if (i != 2 || data[1] != 0) {
195                /* RFC 1929 specifies that the connection MUST be closed if
196                   authentication fails */
197                out.close();
198                in.close();
199                return false;
200            }
201            /* Authentication succeeded */
202            return true;
203        }
204        /**
205         * GSSAPI authentication mechanism.
206         * Unfortunately the RFC seems out of sync with the Reference
207         * implementation. I'll leave this in for future completion.
208         */
209//      if (method == GSSAPI) {
210//          try {
211//              GSSManager manager = GSSManager.getInstance();
212//              GSSName name = manager.createName("SERVICE:socks@"+server,
213//                                                   null);
214//              GSSContext context = manager.createContext(name, null, null,
215//                                                         GSSContext.DEFAULT_LIFETIME);
216//              context.requestMutualAuth(true);
217//              context.requestReplayDet(true);
218//              context.requestSequenceDet(true);
219//              context.requestCredDeleg(true);
220//              byte []inToken = new byte[0];
221//              while (!context.isEstablished()) {
222//                  byte[] outToken
223//                      = context.initSecContext(inToken, 0, inToken.length);
224//                  // send the output token if generated
225//                  if (outToken != null) {
226//                      out.write(1);
227//                      out.write(1);
228//                      out.writeShort(outToken.length);
229//                      out.write(outToken);
230//                      out.flush();
231//                      data = new byte[2];
232//                      i = readSocksReply(in, data, deadlineMillis);
233//                      if (i != 2 || data[1] == 0xff) {
234//                          in.close();
235//                          out.close();
236//                          return false;
237//                      }
238//                      i = readSocksReply(in, data, deadlineMillis);
239//                      int len = 0;
240//                      len = ((int)data[0] & 0xff) << 8;
241//                      len += data[1];
242//                      data = new byte[len];
243//                      i = readSocksReply(in, data, deadlineMillis);
244//                      if (i == len)
245//                          return true;
246//                      in.close();
247//                      out.close();
248//                  }
249//              }
250//          } catch (GSSException e) {
251//              /* RFC 1961 states that if Context initialisation fails the connection
252//                 MUST be closed */
253//              e.printStackTrace();
254//              in.close();
255//              out.close();
256//          }
257//      }
258        return false;
259    }
260
261    private void connectV4(InputStream in, OutputStream out,
262                           InetSocketAddress endpoint,
263                           long deadlineMillis) throws IOException {
264        if (!(endpoint.getAddress() instanceof Inet4Address)) {
265            throw new SocketException("SOCKS V4 requires IPv4 only addresses");
266        }
267        out.write(PROTO_VERS4);
268        out.write(CONNECT);
269        out.write((endpoint.getPort() >> 8) & 0xff);
270        out.write((endpoint.getPort() >> 0) & 0xff);
271        out.write(endpoint.getAddress().getAddress());
272        String userName = getUserName();
273        try {
274            out.write(userName.getBytes("ISO-8859-1"));
275        } catch (java.io.UnsupportedEncodingException uee) {
276            assert false;
277        }
278        out.write(0);
279        out.flush();
280        byte[] data = new byte[8];
281        int n = readSocksReply(in, data, deadlineMillis);
282        if (n != 8)
283            throw new SocketException("Reply from SOCKS server has bad length: " + n);
284        if (data[0] != 0 && data[0] != 4)
285            throw new SocketException("Reply from SOCKS server has bad version");
286        SocketException ex = null;
287        switch (data[1]) {
288        case 90:
289            // Success!
290            external_address = endpoint;
291            break;
292        case 91:
293            ex = new SocketException("SOCKS request rejected");
294            break;
295        case 92:
296            ex = new SocketException("SOCKS server couldn't reach destination");
297            break;
298        case 93:
299            ex = new SocketException("SOCKS authentication failed");
300            break;
301        default:
302            ex = new SocketException("Reply from SOCKS server contains bad status");
303            break;
304        }
305        if (ex != null) {
306            in.close();
307            out.close();
308            throw ex;
309        }
310    }
311
312    /**
313     * Connects the Socks Socket to the specified endpoint. It will first
314     * connect to the SOCKS proxy and negotiate the access. If the proxy
315     * grants the connections, then the connect is successful and all
316     * further traffic will go to the "real" endpoint.
317     *
318     * @param   endpoint        the {@code SocketAddress} to connect to.
319     * @param   timeout         the timeout value in milliseconds
320     * @throws  IOException     if the connection can't be established.
321     * @throws  SecurityException if there is a security manager and it
322     *                          doesn't allow the connection
323     * @throws  IllegalArgumentException if endpoint is null or a
324     *          SocketAddress subclass not supported by this socket
325     */
326    @Override
327    protected void connect(SocketAddress endpoint, int timeout) throws IOException {
328        final long deadlineMillis;
329
330        if (timeout == 0) {
331            deadlineMillis = 0L;
332        } else {
333            long finish = System.currentTimeMillis() + timeout;
334            deadlineMillis = finish < 0 ? Long.MAX_VALUE : finish;
335        }
336
337        SecurityManager security = System.getSecurityManager();
338        if (endpoint == null || !(endpoint instanceof InetSocketAddress))
339            throw new IllegalArgumentException("Unsupported address type");
340        InetSocketAddress epoint = (InetSocketAddress) endpoint;
341        if (security != null) {
342            if (epoint.isUnresolved())
343                security.checkConnect(epoint.getHostName(),
344                                      epoint.getPort());
345            else
346                security.checkConnect(epoint.getAddress().getHostAddress(),
347                                      epoint.getPort());
348        }
349        if (server == null) {
350            /*
351             * Android-changed: Removed code that tried to establish proxy connection if
352             * ProxySelector#getDefault() is not null.
353             * This was never the case in previous android releases, was causing
354             * issues and therefore was removed.
355             */
356            super.connect(epoint, remainingMillis(deadlineMillis));
357            return;
358        } else {
359            // Connects to the SOCKS server
360            try {
361                privilegedConnect(server, serverPort, remainingMillis(deadlineMillis));
362            } catch (IOException e) {
363                throw new SocketException(e.getMessage());
364            }
365        }
366
367        // cmdIn & cmdOut were intialized during the privilegedConnect() call
368        BufferedOutputStream out = new BufferedOutputStream(cmdOut, 512);
369        InputStream in = cmdIn;
370
371        if (useV4) {
372            // SOCKS Protocol version 4 doesn't know how to deal with
373            // DOMAIN type of addresses (unresolved addresses here)
374            if (epoint.isUnresolved())
375                throw new UnknownHostException(epoint.toString());
376            connectV4(in, out, epoint, deadlineMillis);
377            return;
378        }
379
380        // This is SOCKS V5
381        out.write(PROTO_VERS);
382        out.write(2);
383        out.write(NO_AUTH);
384        out.write(USER_PASSW);
385        out.flush();
386        byte[] data = new byte[2];
387        int i = readSocksReply(in, data, deadlineMillis);
388        if (i != 2 || ((int)data[0]) != PROTO_VERS) {
389            // Maybe it's not a V5 sever after all
390            // Let's try V4 before we give up
391            // SOCKS Protocol version 4 doesn't know how to deal with
392            // DOMAIN type of addresses (unresolved addresses here)
393            if (epoint.isUnresolved())
394                throw new UnknownHostException(epoint.toString());
395            connectV4(in, out, epoint, deadlineMillis);
396            return;
397        }
398        if (((int)data[1]) == NO_METHODS)
399            throw new SocketException("SOCKS : No acceptable methods");
400        if (!authenticate(data[1], in, out, deadlineMillis)) {
401            throw new SocketException("SOCKS : authentication failed");
402        }
403        out.write(PROTO_VERS);
404        out.write(CONNECT);
405        out.write(0);
406        /* Test for IPV4/IPV6/Unresolved */
407        if (epoint.isUnresolved()) {
408            out.write(DOMAIN_NAME);
409            out.write(epoint.getHostName().length());
410            try {
411                out.write(epoint.getHostName().getBytes("ISO-8859-1"));
412            } catch (java.io.UnsupportedEncodingException uee) {
413                assert false;
414            }
415            out.write((epoint.getPort() >> 8) & 0xff);
416            out.write((epoint.getPort() >> 0) & 0xff);
417        } else if (epoint.getAddress() instanceof Inet6Address) {
418            out.write(IPV6);
419            out.write(epoint.getAddress().getAddress());
420            out.write((epoint.getPort() >> 8) & 0xff);
421            out.write((epoint.getPort() >> 0) & 0xff);
422        } else {
423            out.write(IPV4);
424            out.write(epoint.getAddress().getAddress());
425            out.write((epoint.getPort() >> 8) & 0xff);
426            out.write((epoint.getPort() >> 0) & 0xff);
427        }
428        out.flush();
429        data = new byte[4];
430        i = readSocksReply(in, data, deadlineMillis);
431        if (i != 4)
432            throw new SocketException("Reply from SOCKS server has bad length");
433        SocketException ex = null;
434        int len;
435        byte[] addr;
436        switch (data[1]) {
437        case REQUEST_OK:
438            // success!
439            switch(data[3]) {
440            case IPV4:
441                addr = new byte[4];
442                i = readSocksReply(in, addr, deadlineMillis);
443                if (i != 4)
444                    throw new SocketException("Reply from SOCKS server badly formatted");
445                data = new byte[2];
446                i = readSocksReply(in, data, deadlineMillis);
447                if (i != 2)
448                    throw new SocketException("Reply from SOCKS server badly formatted");
449                break;
450            case DOMAIN_NAME:
451                len = data[1];
452                byte[] host = new byte[len];
453                i = readSocksReply(in, host, deadlineMillis);
454                if (i != len)
455                    throw new SocketException("Reply from SOCKS server badly formatted");
456                data = new byte[2];
457                i = readSocksReply(in, data, deadlineMillis);
458                if (i != 2)
459                    throw new SocketException("Reply from SOCKS server badly formatted");
460                break;
461            case IPV6:
462                len = data[1];
463                addr = new byte[len];
464                i = readSocksReply(in, addr, deadlineMillis);
465                if (i != len)
466                    throw new SocketException("Reply from SOCKS server badly formatted");
467                data = new byte[2];
468                i = readSocksReply(in, data, deadlineMillis);
469                if (i != 2)
470                    throw new SocketException("Reply from SOCKS server badly formatted");
471                break;
472            default:
473                ex = new SocketException("Reply from SOCKS server contains wrong code");
474                break;
475            }
476            break;
477        case GENERAL_FAILURE:
478            ex = new SocketException("SOCKS server general failure");
479            break;
480        case NOT_ALLOWED:
481            ex = new SocketException("SOCKS: Connection not allowed by ruleset");
482            break;
483        case NET_UNREACHABLE:
484            ex = new SocketException("SOCKS: Network unreachable");
485            break;
486        case HOST_UNREACHABLE:
487            ex = new SocketException("SOCKS: Host unreachable");
488            break;
489        case CONN_REFUSED:
490            ex = new SocketException("SOCKS: Connection refused");
491            break;
492        case TTL_EXPIRED:
493            ex =  new SocketException("SOCKS: TTL expired");
494            break;
495        case CMD_NOT_SUPPORTED:
496            ex = new SocketException("SOCKS: Command not supported");
497            break;
498        case ADDR_TYPE_NOT_SUP:
499            ex = new SocketException("SOCKS: address type not supported");
500            break;
501        }
502        if (ex != null) {
503            in.close();
504            out.close();
505            throw ex;
506        }
507        external_address = epoint;
508    }
509
510    /**
511     * Returns the value of this socket's {@code address} field.
512     *
513     * @return  the value of this socket's {@code address} field.
514     * @see     java.net.SocketImpl#address
515     */
516    @Override
517    protected InetAddress getInetAddress() {
518        if (external_address != null)
519            return external_address.getAddress();
520        else
521            return super.getInetAddress();
522    }
523
524    /**
525     * Returns the value of this socket's {@code port} field.
526     *
527     * @return  the value of this socket's {@code port} field.
528     * @see     java.net.SocketImpl#port
529     */
530    @Override
531    protected int getPort() {
532        if (external_address != null)
533            return external_address.getPort();
534        else
535            return super.getPort();
536    }
537
538    @Override
539    protected int getLocalPort() {
540        if (socket != null)
541            return super.getLocalPort();
542        if (external_address != null)
543            return external_address.getPort();
544        else
545            return super.getLocalPort();
546    }
547
548    @Override
549    protected void close() throws IOException {
550        if (cmdsock != null)
551            cmdsock.close();
552        cmdsock = null;
553        super.close();
554    }
555
556    private String getUserName() {
557        String userName = "";
558        if (applicationSetProxy) {
559            try {
560                userName = System.getProperty("user.name");
561            } catch (SecurityException se) { /* swallow Exception */ }
562        } else {
563            userName = java.security.AccessController.doPrivileged(
564                new sun.security.action.GetPropertyAction("user.name"));
565        }
566        return userName;
567    }
568}
569