blob: 23ef62328073755ce4863de2c4cf84c558aa5ad0 [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
2 * $RCSfile$
3 * $Revision$
4 * $Date$
5 *
6 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18package org.jivesoftware.smack.proxy;
19
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.OutputStream;
23import java.net.InetAddress;
24import java.net.Socket;
25import java.net.UnknownHostException;
26import javax.net.SocketFactory;
27
28/**
29 * Socket factory for Socks5 proxy
30 *
31 * @author Atul Aggarwal
32 */
33public class Socks5ProxySocketFactory
34 extends SocketFactory
35{
36 private ProxyInfo proxy;
37
38 public Socks5ProxySocketFactory(ProxyInfo proxy)
39 {
40 this.proxy = proxy;
41 }
42
43 public Socket createSocket(String host, int port)
44 throws IOException, UnknownHostException
45 {
46 return socks5ProxifiedSocket(host,port);
47 }
48
49 public Socket createSocket(String host ,int port, InetAddress localHost,
50 int localPort)
51 throws IOException, UnknownHostException
52 {
53
54 return socks5ProxifiedSocket(host,port);
55
56 }
57
58 public Socket createSocket(InetAddress host, int port)
59 throws IOException
60 {
61
62 return socks5ProxifiedSocket(host.getHostAddress(),port);
63
64 }
65
66 public Socket createSocket( InetAddress address, int port,
67 InetAddress localAddress, int localPort)
68 throws IOException
69 {
70
71 return socks5ProxifiedSocket(address.getHostAddress(),port);
72
73 }
74
75 private Socket socks5ProxifiedSocket(String host, int port)
76 throws IOException
77 {
78 Socket socket = null;
79 InputStream in = null;
80 OutputStream out = null;
81 String proxy_host = proxy.getProxyAddress();
82 int proxy_port = proxy.getProxyPort();
83 String user = proxy.getProxyUsername();
84 String passwd = proxy.getProxyPassword();
85
86 try
87 {
88 socket=new Socket(proxy_host, proxy_port);
89 in=socket.getInputStream();
90 out=socket.getOutputStream();
91
92 socket.setTcpNoDelay(true);
93
94 byte[] buf=new byte[1024];
95 int index=0;
96
97/*
98 +----+----------+----------+
99 |VER | NMETHODS | METHODS |
100 +----+----------+----------+
101 | 1 | 1 | 1 to 255 |
102 +----+----------+----------+
103
104 The VER field is set to X'05' for this version of the protocol. The
105 NMETHODS field contains the number of method identifier octets that
106 appear in the METHODS field.
107
108 The values currently defined for METHOD are:
109
110 o X'00' NO AUTHENTICATION REQUIRED
111 o X'01' GSSAPI
112 o X'02' USERNAME/PASSWORD
113 o X'03' to X'7F' IANA ASSIGNED
114 o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
115 o X'FF' NO ACCEPTABLE METHODS
116*/
117
118 buf[index++]=5;
119
120 buf[index++]=2;
121 buf[index++]=0; // NO AUTHENTICATION REQUIRED
122 buf[index++]=2; // USERNAME/PASSWORD
123
124 out.write(buf, 0, index);
125
126/*
127 The server selects from one of the methods given in METHODS, and
128 sends a METHOD selection message:
129
130 +----+--------+
131 |VER | METHOD |
132 +----+--------+
133 | 1 | 1 |
134 +----+--------+
135*/
136 //in.read(buf, 0, 2);
137 fill(in, buf, 2);
138
139 boolean check=false;
140 switch((buf[1])&0xff)
141 {
142 case 0: // NO AUTHENTICATION REQUIRED
143 check=true;
144 break;
145 case 2: // USERNAME/PASSWORD
146 if(user==null || passwd==null)
147 {
148 break;
149 }
150
151/*
152 Once the SOCKS V5 server has started, and the client has selected the
153 Username/Password Authentication protocol, the Username/Password
154 subnegotiation begins. This begins with the client producing a
155 Username/Password request:
156
157 +----+------+----------+------+----------+
158 |VER | ULEN | UNAME | PLEN | PASSWD |
159 +----+------+----------+------+----------+
160 | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
161 +----+------+----------+------+----------+
162
163 The VER field contains the current version of the subnegotiation,
164 which is X'01'. The ULEN field contains the length of the UNAME field
165 that follows. The UNAME field contains the username as known to the
166 source operating system. The PLEN field contains the length of the
167 PASSWD field that follows. The PASSWD field contains the password
168 association with the given UNAME.
169*/
170 index=0;
171 buf[index++]=1;
172 buf[index++]=(byte)(user.length());
173 System.arraycopy(user.getBytes(), 0, buf, index,
174 user.length());
175 index+=user.length();
176 buf[index++]=(byte)(passwd.length());
177 System.arraycopy(passwd.getBytes(), 0, buf, index,
178 passwd.length());
179 index+=passwd.length();
180
181 out.write(buf, 0, index);
182
183/*
184 The server verifies the supplied UNAME and PASSWD, and sends the
185 following response:
186
187 +----+--------+
188 |VER | STATUS |
189 +----+--------+
190 | 1 | 1 |
191 +----+--------+
192
193 A STATUS field of X'00' indicates success. If the server returns a
194 `failure' (STATUS value other than X'00') status, it MUST close the
195 connection.
196*/
197 //in.read(buf, 0, 2);
198 fill(in, buf, 2);
199 if(buf[1]==0)
200 {
201 check=true;
202 }
203 break;
204 default:
205 }
206
207 if(!check)
208 {
209 try
210 {
211 socket.close();
212 }
213 catch(Exception eee)
214 {
215 }
216 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,
217 "fail in SOCKS5 proxy");
218 }
219
220/*
221 The SOCKS request is formed as follows:
222
223 +----+-----+-------+------+----------+----------+
224 |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
225 +----+-----+-------+------+----------+----------+
226 | 1 | 1 | X'00' | 1 | Variable | 2 |
227 +----+-----+-------+------+----------+----------+
228
229 Where:
230
231 o VER protocol version: X'05'
232 o CMD
233 o CONNECT X'01'
234 o BIND X'02'
235 o UDP ASSOCIATE X'03'
236 o RSV RESERVED
237 o ATYP address type of following address
238 o IP V4 address: X'01'
239 o DOMAINNAME: X'03'
240 o IP V6 address: X'04'
241 o DST.ADDR desired destination address
242 o DST.PORT desired destination port in network octet
243 order
244*/
245
246 index=0;
247 buf[index++]=5;
248 buf[index++]=1; // CONNECT
249 buf[index++]=0;
250
251 byte[] hostb=host.getBytes();
252 int len=hostb.length;
253 buf[index++]=3; // DOMAINNAME
254 buf[index++]=(byte)(len);
255 System.arraycopy(hostb, 0, buf, index, len);
256 index+=len;
257 buf[index++]=(byte)(port>>>8);
258 buf[index++]=(byte)(port&0xff);
259
260 out.write(buf, 0, index);
261
262/*
263 The SOCKS request information is sent by the client as soon as it has
264 established a connection to the SOCKS server, and completed the
265 authentication negotiations. The server evaluates the request, and
266 returns a reply formed as follows:
267
268 +----+-----+-------+------+----------+----------+
269 |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
270 +----+-----+-------+------+----------+----------+
271 | 1 | 1 | X'00' | 1 | Variable | 2 |
272 +----+-----+-------+------+----------+----------+
273
274 Where:
275
276 o VER protocol version: X'05'
277 o REP Reply field:
278 o X'00' succeeded
279 o X'01' general SOCKS server failure
280 o X'02' connection not allowed by ruleset
281 o X'03' Network unreachable
282 o X'04' Host unreachable
283 o X'05' Connection refused
284 o X'06' TTL expired
285 o X'07' Command not supported
286 o X'08' Address type not supported
287 o X'09' to X'FF' unassigned
288 o RSV RESERVED
289 o ATYP address type of following address
290 o IP V4 address: X'01'
291 o DOMAINNAME: X'03'
292 o IP V6 address: X'04'
293 o BND.ADDR server bound address
294 o BND.PORT server bound port in network octet order
295*/
296
297 //in.read(buf, 0, 4);
298 fill(in, buf, 4);
299
300 if(buf[1]!=0)
301 {
302 try
303 {
304 socket.close();
305 }
306 catch(Exception eee)
307 {
308 }
309 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,
310 "server returns "+buf[1]);
311 }
312
313 switch(buf[3]&0xff)
314 {
315 case 1:
316 //in.read(buf, 0, 6);
317 fill(in, buf, 6);
318 break;
319 case 3:
320 //in.read(buf, 0, 1);
321 fill(in, buf, 1);
322 //in.read(buf, 0, buf[0]+2);
323 fill(in, buf, (buf[0]&0xff)+2);
324 break;
325 case 4:
326 //in.read(buf, 0, 18);
327 fill(in, buf, 18);
328 break;
329 default:
330 }
331 return socket;
332
333 }
334 catch(RuntimeException e)
335 {
336 throw e;
337 }
338 catch(Exception e)
339 {
340 try
341 {
342 if(socket!=null)
343 {
344 socket.close();
345 }
346 }
347 catch(Exception eee)
348 {
349 }
350 String message="ProxySOCKS5: "+e.toString();
351 if(e instanceof Throwable)
352 {
353 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,message,
354 (Throwable)e);
355 }
356 throw new IOException(message);
357 }
358 }
359
360 private void fill(InputStream in, byte[] buf, int len)
361 throws IOException
362 {
363 int s=0;
364 while(s<len)
365 {
366 int i=in.read(buf, s, len-s);
367 if(i<=0)
368 {
369 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "stream " +
370 "is closed");
371 }
372 s+=i;
373 }
374 }
375}