Shuyi Chen | d7955ce | 2013-05-22 14:51:55 -0700 | [diff] [blame] | 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 | } |