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