001/** 002 * 003 * Copyright the original author or authors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.proxy; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022import java.net.InetAddress; 023import java.net.Socket; 024import java.net.UnknownHostException; 025import javax.net.SocketFactory; 026 027/** 028 * Socket factory for Socks5 proxy 029 * 030 * @author Atul Aggarwal 031 */ 032public class Socks5ProxySocketFactory 033 extends SocketFactory 034{ 035 private ProxyInfo proxy; 036 037 public Socks5ProxySocketFactory(ProxyInfo proxy) 038 { 039 this.proxy = proxy; 040 } 041 042 public Socket createSocket(String host, int port) 043 throws IOException, UnknownHostException 044 { 045 return socks5ProxifiedSocket(host,port); 046 } 047 048 public Socket createSocket(String host ,int port, InetAddress localHost, 049 int localPort) 050 throws IOException, UnknownHostException 051 { 052 053 return socks5ProxifiedSocket(host,port); 054 055 } 056 057 public Socket createSocket(InetAddress host, int port) 058 throws IOException 059 { 060 061 return socks5ProxifiedSocket(host.getHostAddress(),port); 062 063 } 064 065 public Socket createSocket( InetAddress address, int port, 066 InetAddress localAddress, int localPort) 067 throws IOException 068 { 069 070 return socks5ProxifiedSocket(address.getHostAddress(),port); 071 072 } 073 074 private Socket socks5ProxifiedSocket(String host, int port) 075 throws IOException 076 { 077 Socket socket = null; 078 InputStream in = null; 079 OutputStream out = null; 080 String proxy_host = proxy.getProxyAddress(); 081 int proxy_port = proxy.getProxyPort(); 082 String user = proxy.getProxyUsername(); 083 String passwd = proxy.getProxyPassword(); 084 085 try 086 { 087 socket=new Socket(proxy_host, proxy_port); 088 in=socket.getInputStream(); 089 out=socket.getOutputStream(); 090 091 socket.setTcpNoDelay(true); 092 093 byte[] buf=new byte[1024]; 094 int index=0; 095 096/* 097 +----+----------+----------+ 098 |VER | NMETHODS | METHODS | 099 +----+----------+----------+ 100 | 1 | 1 | 1 to 255 | 101 +----+----------+----------+ 102 103 The VER field is set to X'05' for this version of the protocol. The 104 NMETHODS field contains the number of method identifier octets that 105 appear in the METHODS field. 106 107 The values currently defined for METHOD are: 108 109 o X'00' NO AUTHENTICATION REQUIRED 110 o X'01' GSSAPI 111 o X'02' USERNAME/PASSWORD 112 o X'03' to X'7F' IANA ASSIGNED 113 o X'80' to X'FE' RESERVED FOR PRIVATE METHODS 114 o X'FF' NO ACCEPTABLE METHODS 115*/ 116 117 buf[index++]=5; 118 119 buf[index++]=2; 120 buf[index++]=0; // NO AUTHENTICATION REQUIRED 121 buf[index++]=2; // USERNAME/PASSWORD 122 123 out.write(buf, 0, index); 124 125/* 126 The server selects from one of the methods given in METHODS, and 127 sends a METHOD selection message: 128 129 +----+--------+ 130 |VER | METHOD | 131 +----+--------+ 132 | 1 | 1 | 133 +----+--------+ 134*/ 135 //in.read(buf, 0, 2); 136 fill(in, buf, 2); 137 138 boolean check=false; 139 switch((buf[1])&0xff) 140 { 141 case 0: // NO AUTHENTICATION REQUIRED 142 check=true; 143 break; 144 case 2: // USERNAME/PASSWORD 145 if(user==null || passwd==null) 146 { 147 break; 148 } 149 150/* 151 Once the SOCKS V5 server has started, and the client has selected the 152 Username/Password Authentication protocol, the Username/Password 153 subnegotiation begins. This begins with the client producing a 154 Username/Password request: 155 156 +----+------+----------+------+----------+ 157 |VER | ULEN | UNAME | PLEN | PASSWD | 158 +----+------+----------+------+----------+ 159 | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 160 +----+------+----------+------+----------+ 161 162 The VER field contains the current version of the subnegotiation, 163 which is X'01'. The ULEN field contains the length of the UNAME field 164 that follows. The UNAME field contains the username as known to the 165 source operating system. The PLEN field contains the length of the 166 PASSWD field that follows. The PASSWD field contains the password 167 association with the given UNAME. 168*/ 169 index=0; 170 buf[index++]=1; 171 buf[index++]=(byte)(user.length()); 172 System.arraycopy(user.getBytes(), 0, buf, index, 173 user.length()); 174 index+=user.length(); 175 buf[index++]=(byte)(passwd.length()); 176 System.arraycopy(passwd.getBytes(), 0, buf, index, 177 passwd.length()); 178 index+=passwd.length(); 179 180 out.write(buf, 0, index); 181 182/* 183 The server verifies the supplied UNAME and PASSWD, and sends the 184 following response: 185 186 +----+--------+ 187 |VER | STATUS | 188 +----+--------+ 189 | 1 | 1 | 190 +----+--------+ 191 192 A STATUS field of X'00' indicates success. If the server returns a 193 `failure' (STATUS value other than X'00') status, it MUST close the 194 connection. 195*/ 196 //in.read(buf, 0, 2); 197 fill(in, buf, 2); 198 if(buf[1]==0) 199 { 200 check=true; 201 } 202 break; 203 default: 204 } 205 206 if(!check) 207 { 208 try 209 { 210 socket.close(); 211 } 212 catch(Exception eee) 213 { 214 } 215 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, 216 "fail in SOCKS5 proxy"); 217 } 218 219/* 220 The SOCKS request is formed as follows: 221 222 +----+-----+-------+------+----------+----------+ 223 |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 224 +----+-----+-------+------+----------+----------+ 225 | 1 | 1 | X'00' | 1 | Variable | 2 | 226 +----+-----+-------+------+----------+----------+ 227 228 Where: 229 230 o VER protocol version: X'05' 231 o CMD 232 o CONNECT X'01' 233 o BIND X'02' 234 o UDP ASSOCIATE X'03' 235 o RSV RESERVED 236 o ATYP address type of following address 237 o IP V4 address: X'01' 238 o DOMAINNAME: X'03' 239 o IP V6 address: X'04' 240 o DST.ADDR desired destination address 241 o DST.PORT desired destination port in network octet 242 order 243*/ 244 245 index=0; 246 buf[index++]=5; 247 buf[index++]=1; // CONNECT 248 buf[index++]=0; 249 250 byte[] hostb=host.getBytes(); 251 int len=hostb.length; 252 buf[index++]=3; // DOMAINNAME 253 buf[index++]=(byte)(len); 254 System.arraycopy(hostb, 0, buf, index, len); 255 index+=len; 256 buf[index++]=(byte)(port>>>8); 257 buf[index++]=(byte)(port&0xff); 258 259 out.write(buf, 0, index); 260 261/* 262 The SOCKS request information is sent by the client as soon as it has 263 established a connection to the SOCKS server, and completed the 264 authentication negotiations. The server evaluates the request, and 265 returns a reply formed as follows: 266 267 +----+-----+-------+------+----------+----------+ 268 |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 269 +----+-----+-------+------+----------+----------+ 270 | 1 | 1 | X'00' | 1 | Variable | 2 | 271 +----+-----+-------+------+----------+----------+ 272 273 Where: 274 275 o VER protocol version: X'05' 276 o REP Reply field: 277 o X'00' succeeded 278 o X'01' general SOCKS server failure 279 o X'02' connection not allowed by ruleset 280 o X'03' Network unreachable 281 o X'04' Host unreachable 282 o X'05' XMPPConnection refused 283 o X'06' TTL expired 284 o X'07' Command not supported 285 o X'08' Address type not supported 286 o X'09' to X'FF' unassigned 287 o RSV RESERVED 288 o ATYP address type of following address 289 o IP V4 address: X'01' 290 o DOMAINNAME: X'03' 291 o IP V6 address: X'04' 292 o BND.ADDR server bound address 293 o BND.PORT server bound port in network octet order 294*/ 295 296 //in.read(buf, 0, 4); 297 fill(in, buf, 4); 298 299 if(buf[1]!=0) 300 { 301 try 302 { 303 socket.close(); 304 } 305 catch(Exception eee) 306 { 307 } 308 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, 309 "server returns "+buf[1]); 310 } 311 312 switch(buf[3]&0xff) 313 { 314 case 1: 315 //in.read(buf, 0, 6); 316 fill(in, buf, 6); 317 break; 318 case 3: 319 //in.read(buf, 0, 1); 320 fill(in, buf, 1); 321 //in.read(buf, 0, buf[0]+2); 322 fill(in, buf, (buf[0]&0xff)+2); 323 break; 324 case 4: 325 //in.read(buf, 0, 18); 326 fill(in, buf, 18); 327 break; 328 default: 329 } 330 return socket; 331 332 } 333 catch(RuntimeException e) 334 { 335 throw e; 336 } 337 catch(Exception e) 338 { 339 try 340 { 341 if(socket!=null) 342 { 343 socket.close(); 344 } 345 } 346 catch(Exception eee) 347 { 348 } 349 String message="ProxySOCKS5: "+e.toString(); 350 if(e instanceof Throwable) 351 { 352 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,message, 353 (Throwable)e); 354 } 355 throw new IOException(message); 356 } 357 } 358 359 private void fill(InputStream in, byte[] buf, int len) 360 throws IOException 361 { 362 int s=0; 363 while(s<len) 364 { 365 int i=in.read(buf, s, len-s); 366 if(i<=0) 367 { 368 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "stream " + 369 "is closed"); 370 } 371 s+=i; 372 } 373 } 374}