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.InetSocketAddress; 023import java.net.Socket; 024 025import org.jivesoftware.smack.util.StringUtils; 026 027/** 028 * Socket factory for Socks5 proxy. 029 * 030 * @author Atul Aggarwal 031 */ 032public class Socks5ProxySocketConnection implements ProxySocketConnection { 033 private final ProxyInfo proxy; 034 035 Socks5ProxySocketConnection(ProxyInfo proxy) 036 { 037 this.proxy = proxy; 038 } 039 040 @Override 041 public void connect(Socket socket, String host, int port, int timeout) 042 throws IOException { 043 InputStream in = null; 044 OutputStream out = null; 045 String proxy_host = proxy.getProxyAddress(); 046 int proxy_port = proxy.getProxyPort(); 047 String user = proxy.getProxyUsername(); 048 String passwd = proxy.getProxyPassword(); 049 050 try 051 { 052 socket.connect(new InetSocketAddress(proxy_host, proxy_port), timeout); 053 in = socket.getInputStream(); 054 out = socket.getOutputStream(); 055 056 socket.setTcpNoDelay(true); 057 058 byte[] buf = new byte[1024]; 059 int index = 0; 060 061/* 062 +----+----------+----------+ 063 |VER | NMETHODS | METHODS | 064 +----+----------+----------+ 065 | 1 | 1 | 1 to 255 | 066 +----+----------+----------+ 067 068 The VER field is set to X'05' for this version of the protocol. The 069 NMETHODS field contains the number of method identifier octets that 070 appear in the METHODS field. 071 072 The values currently defined for METHOD are: 073 074 o X'00' NO AUTHENTICATION REQUIRED 075 o X'01' GSSAPI 076 o X'02' USERNAME/PASSWORD 077 o X'03' to X'7F' IANA ASSIGNED 078 o X'80' to X'FE' RESERVED FOR PRIVATE METHODS 079 o X'FF' NO ACCEPTABLE METHODS 080*/ 081 082 buf[index++] = 5; 083 084 buf[index++] = 2; 085 buf[index++] = 0; // NO AUTHENTICATION REQUIRED 086 buf[index++] = 2; // USERNAME/PASSWORD 087 088 out.write(buf, 0, index); 089 090/* 091 The server selects from one of the methods given in METHODS, and 092 sends a METHOD selection message: 093 094 +----+--------+ 095 |VER | METHOD | 096 +----+--------+ 097 | 1 | 1 | 098 +----+--------+ 099*/ 100 //in.read(buf, 0, 2); 101 fill(in, buf, 2); 102 103 boolean check = false; 104 switch ((buf[1]) & 0xff) 105 { 106 case 0: // NO AUTHENTICATION REQUIRED 107 check = true; 108 break; 109 case 2: // USERNAME/PASSWORD 110 if (user == null || passwd == null) 111 { 112 break; 113 } 114 115/* 116 Once the SOCKS V5 server has started, and the client has selected the 117 Username/Password Authentication protocol, the Username/Password 118 subnegotiation begins. This begins with the client producing a 119 Username/Password request: 120 121 +----+------+----------+------+----------+ 122 |VER | ULEN | UNAME | PLEN | PASSWD | 123 +----+------+----------+------+----------+ 124 | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 125 +----+------+----------+------+----------+ 126 127 The VER field contains the current version of the subnegotiation, 128 which is X'01'. The ULEN field contains the length of the UNAME field 129 that follows. The UNAME field contains the username as known to the 130 source operating system. The PLEN field contains the length of the 131 PASSWD field that follows. The PASSWD field contains the password 132 association with the given UNAME. 133*/ 134 index = 0; 135 buf[index++] = 1; 136 buf[index++] = (byte) (user.length()); 137 byte[] userBytes = user.getBytes(StringUtils.UTF8); 138 System.arraycopy(userBytes, 0, buf, index, 139 user.length()); 140 index += user.length(); 141 byte[] passwordBytes = user.getBytes(StringUtils.UTF8); 142 buf[index++] = (byte) (passwordBytes.length); 143 System.arraycopy(passwordBytes, 0, buf, index, 144 passwd.length()); 145 index += passwd.length(); 146 147 out.write(buf, 0, index); 148 149/* 150 The server verifies the supplied UNAME and PASSWD, and sends the 151 following response: 152 153 +----+--------+ 154 |VER | STATUS | 155 +----+--------+ 156 | 1 | 1 | 157 +----+--------+ 158 159 A STATUS field of X'00' indicates success. If the server returns a 160 `failure' (STATUS value other than X'00') status, it MUST close the 161 connection. 162*/ 163 //in.read(buf, 0, 2); 164 fill(in, buf, 2); 165 if (buf[1] == 0) 166 { 167 check = true; 168 } 169 break; 170 default: 171 } 172 173 if (!check) 174 { 175 try 176 { 177 socket.close(); 178 } 179 catch (Exception eee) 180 { 181 } 182 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, 183 "fail in SOCKS5 proxy"); 184 } 185 186/* 187 The SOCKS request is formed as follows: 188 189 +----+-----+-------+------+----------+----------+ 190 |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 191 +----+-----+-------+------+----------+----------+ 192 | 1 | 1 | X'00' | 1 | Variable | 2 | 193 +----+-----+-------+------+----------+----------+ 194 195 Where: 196 197 o VER protocol version: X'05' 198 o CMD 199 o CONNECT X'01' 200 o BIND X'02' 201 o UDP ASSOCIATE X'03' 202 o RSV RESERVED 203 o ATYP address type of following address 204 o IP V4 address: X'01' 205 o DOMAINNAME: X'03' 206 o IP V6 address: X'04' 207 o DST.ADDR desired destination address 208 o DST.PORT desired destination port in network octet 209 order 210*/ 211 212 index = 0; 213 buf[index++] = 5; 214 buf[index++] = 1; // CONNECT 215 buf[index++] = 0; 216 217 byte[] hostb = host.getBytes(StringUtils.UTF8); 218 int len = hostb.length; 219 buf[index++] = 3; // DOMAINNAME 220 buf[index++] = (byte) (len); 221 System.arraycopy(hostb, 0, buf, index, len); 222 index += len; 223 buf[index++] = (byte) (port >>> 8); 224 buf[index++] = (byte) (port & 0xff); 225 226 out.write(buf, 0, index); 227 228/* 229 The SOCKS request information is sent by the client as soon as it has 230 established a connection to the SOCKS server, and completed the 231 authentication negotiations. The server evaluates the request, and 232 returns a reply formed as follows: 233 234 +----+-----+-------+------+----------+----------+ 235 |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 236 +----+-----+-------+------+----------+----------+ 237 | 1 | 1 | X'00' | 1 | Variable | 2 | 238 +----+-----+-------+------+----------+----------+ 239 240 Where: 241 242 o VER protocol version: X'05' 243 o REP Reply field: 244 o X'00' succeeded 245 o X'01' general SOCKS server failure 246 o X'02' connection not allowed by ruleset 247 o X'03' Network unreachable 248 o X'04' Host unreachable 249 o X'05' XMPPConnection refused 250 o X'06' TTL expired 251 o X'07' Command not supported 252 o X'08' Address type not supported 253 o X'09' to X'FF' unassigned 254 o RSV RESERVED 255 o ATYP address type of following address 256 o IP V4 address: X'01' 257 o DOMAINNAME: X'03' 258 o IP V6 address: X'04' 259 o BND.ADDR server bound address 260 o BND.PORT server bound port in network octet order 261*/ 262 263 //in.read(buf, 0, 4); 264 fill(in, buf, 4); 265 266 if (buf[1] != 0) 267 { 268 try 269 { 270 socket.close(); 271 } 272 catch (Exception eee) 273 { 274 } 275 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, 276 "server returns " + buf[1]); 277 } 278 279 switch (buf[3] & 0xff) 280 { 281 case 1: 282 //in.read(buf, 0, 6); 283 fill(in, buf, 6); 284 break; 285 case 3: 286 //in.read(buf, 0, 1); 287 fill(in, buf, 1); 288 //in.read(buf, 0, buf[0]+2); 289 fill(in, buf, (buf[0] & 0xff) + 2); 290 break; 291 case 4: 292 //in.read(buf, 0, 18); 293 fill(in, buf, 18); 294 break; 295 default: 296 } 297 } 298 catch (RuntimeException e) 299 { 300 throw e; 301 } 302 catch (Exception e) 303 { 304 try 305 { 306 socket.close(); 307 } 308 catch (Exception eee) 309 { 310 } 311 // TODO convert to IOException(e) when minimum Android API level is 9 or higher 312 throw new IOException(e.getLocalizedMessage()); 313 } 314 } 315 316 private static void fill(InputStream in, byte[] buf, int len) 317 throws IOException 318 { 319 int s = 0; 320 while (s < len) 321 { 322 int i = in.read(buf, s, len - s); 323 if (i <= 0) 324 { 325 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "stream " + 326 "is closed"); 327 } 328 s += i; 329 } 330 } 331 332}