1d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen/**
2d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
3d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * you may not use this file except in compliance with the License.
4d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * You may obtain a copy of the License at
5d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen *
6d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen *     http://www.apache.org/licenses/LICENSE-2.0
7d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen *
8d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * Unless required by applicable law or agreed to in writing, software
9d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * distributed under the License is distributed on an "AS IS" BASIS,
10d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * See the License for the specific language governing permissions and
12d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * limitations under the License.
13d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen */
14d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenpackage org.jivesoftware.smackx.bytestreams.socks5;
15d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
16d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.io.DataInputStream;
17d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.io.DataOutputStream;
18d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.io.IOException;
19d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.net.InetSocketAddress;
20d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.net.Socket;
21d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.net.SocketAddress;
22d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.Arrays;
23d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.concurrent.Callable;
24d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.concurrent.ExecutionException;
25d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.concurrent.FutureTask;
26d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.concurrent.TimeUnit;
27d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.concurrent.TimeoutException;
28d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
29d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.XMPPException;
30d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost;
31d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
32d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen/**
33d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * The SOCKS5 client class handles establishing a connection to a SOCKS5 proxy. Connecting to a
34d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * SOCKS5 proxy requires authentication. This implementation only supports the no-authentication
35d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * authentication method.
36d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen *
37d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * @author Henning Staib
38d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen */
39d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenclass Socks5Client {
40d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
41d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /* stream host containing network settings and name of the SOCKS5 proxy */
42d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    protected StreamHost streamHost;
43d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
44d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /* SHA-1 digest identifying the SOCKS5 stream */
45d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    protected String digest;
46d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
47d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
48d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Constructor for a SOCKS5 client.
49d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
50d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param streamHost containing network settings of the SOCKS5 proxy
51d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param digest identifying the SOCKS5 Bytestream
52d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
53d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public Socks5Client(StreamHost streamHost, String digest) {
54d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        this.streamHost = streamHost;
55d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        this.digest = digest;
56d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
57d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
58d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
59d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Returns the initialized socket that can be used to transfer data between peers via the SOCKS5
60d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * proxy.
61d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
62d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param timeout timeout to connect to SOCKS5 proxy in milliseconds
63d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return socket the initialized socket
64d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @throws IOException if initializing the socket failed due to a network error
65d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @throws XMPPException if establishing connection to SOCKS5 proxy failed
66d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @throws TimeoutException if connecting to SOCKS5 proxy timed out
67d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @throws InterruptedException if the current thread was interrupted while waiting
68d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
69d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public Socket getSocket(int timeout) throws IOException, XMPPException, InterruptedException,
70d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    TimeoutException {
71d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
72d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // wrap connecting in future for timeout
73d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        FutureTask<Socket> futureTask = new FutureTask<Socket>(new Callable<Socket>() {
74d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
75d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public Socket call() throws Exception {
76d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
77d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // initialize socket
78d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                Socket socket = new Socket();
79d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                SocketAddress socketAddress = new InetSocketAddress(streamHost.getAddress(),
80d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                                streamHost.getPort());
81d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                socket.connect(socketAddress);
82d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
83d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // initialize connection to SOCKS5 proxy
84d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                if (!establish(socket)) {
85d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
86d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    // initialization failed, close socket
87d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    socket.close();
88d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    throw new XMPPException("establishing connection to SOCKS5 proxy failed");
89d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
90d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                }
91d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
92d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                return socket;
93d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
94d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
95d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        });
96d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        Thread executor = new Thread(futureTask);
97d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        executor.start();
98d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
99d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // get connection to initiator with timeout
100d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        try {
101d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return futureTask.get(timeout, TimeUnit.MILLISECONDS);
102d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
103d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        catch (ExecutionException e) {
104d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            Throwable cause = e.getCause();
105d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            if (cause != null) {
106d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // case exceptions to comply with method signature
107d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                if (cause instanceof IOException) {
108d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    throw (IOException) cause;
109d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                }
110d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                if (cause instanceof XMPPException) {
111d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    throw (XMPPException) cause;
112d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                }
113d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
114d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
115d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            // throw generic IO exception if unexpected exception was thrown
116d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            throw new IOException("Error while connection to SOCKS5 proxy");
117d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
118d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
119d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
120d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
121d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
122d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Initializes the connection to the SOCKS5 proxy by negotiating authentication method and
123d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * requesting a stream for the given digest. Currently only the no-authentication method is
124d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * supported by the Socks5Client.
125d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * <p>
126d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Returns <code>true</code> if a stream could be established, otherwise <code>false</code>. If
127d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * <code>false</code> is returned the given Socket should be closed.
128d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
129d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param socket connected to a SOCKS5 proxy
130d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return <code>true</code> if if a stream could be established, otherwise <code>false</code>.
131d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *         If <code>false</code> is returned the given Socket should be closed.
132d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @throws IOException if a network error occurred
133d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
134d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    protected boolean establish(Socket socket) throws IOException {
135d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
136d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        /*
137d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen         * use DataInputStream/DataOutpuStream to assure read and write is completed in a single
138d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen         * statement
139d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen         */
140d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        DataInputStream in = new DataInputStream(socket.getInputStream());
141d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
142d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
143d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // authentication negotiation
144d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        byte[] cmd = new byte[3];
145d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
146d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        cmd[0] = (byte) 0x05; // protocol version 5
147d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        cmd[1] = (byte) 0x01; // number of authentication methods supported
148d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        cmd[2] = (byte) 0x00; // authentication method: no-authentication required
149d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
150d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        out.write(cmd);
151d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        out.flush();
152d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
153d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        byte[] response = new byte[2];
154d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        in.readFully(response);
155d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
156d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // check if server responded with correct version and no-authentication method
157d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (response[0] != (byte) 0x05 || response[1] != (byte) 0x00) {
158d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return false;
159d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
160d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
161d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // request SOCKS5 connection with given address/digest
162d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        byte[] connectionRequest = createSocks5ConnectRequest();
163d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        out.write(connectionRequest);
164d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        out.flush();
165d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
166d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // receive response
167d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        byte[] connectionResponse;
168d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        try {
169d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            connectionResponse = Socks5Utils.receiveSocks5Message(in);
170d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
171d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        catch (XMPPException e) {
172d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return false; // server answered in an unsupported way
173d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
174d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
175d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // verify response
176d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        connectionRequest[1] = (byte) 0x00; // set expected return status to 0
177d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return Arrays.equals(connectionRequest, connectionResponse);
178d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
179d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
180d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
181d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Returns a SOCKS5 connection request message. It contains the command "connect", the address
182d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * type "domain" and the digest as address.
183d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * <p>
184d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * (see <a href="http://tools.ietf.org/html/rfc1928">RFC1928</a>)
185d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
186d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return SOCKS5 connection request message
187d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
188d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private byte[] createSocks5ConnectRequest() {
189d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        byte addr[] = this.digest.getBytes();
190d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
191d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        byte[] data = new byte[7 + addr.length];
192d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        data[0] = (byte) 0x05; // version (SOCKS5)
193d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        data[1] = (byte) 0x01; // command (1 - connect)
194d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        data[2] = (byte) 0x00; // reserved byte (always 0)
195d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        data[3] = (byte) 0x03; // address type (3 - domain name)
196d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        data[4] = (byte) addr.length; // address length
197d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        System.arraycopy(addr, 0, data, 5, addr.length); // address
198d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        data[data.length - 2] = (byte) 0; // address port (2 bytes always 0)
199d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        data[data.length - 1] = (byte) 0;
200d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
201d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return data;
202d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
203d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
204d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen}
205