1 /** 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 */ 18 package org.jivesoftware.smack.proxy; 19 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.io.OutputStream; 23 import java.net.InetAddress; 24 import java.net.Socket; 25 import java.net.UnknownHostException; 26 import javax.net.SocketFactory; 27 28 /** 29 * Socket factory for Socks5 proxy 30 * 31 * @author Atul Aggarwal 32 */ 33 public 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 } 376