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 fill(in, buf, 2); 101 102 boolean check = false; 103 switch ((buf[1]) & 0xff) 104 { 105 case 0: // NO AUTHENTICATION REQUIRED 106 check = true; 107 break; 108 case 2: // USERNAME/PASSWORD 109 if (user == null || passwd == null) 110 { 111 break; 112 } 113 114/* 115 Once the SOCKS V5 server has started, and the client has selected the 116 Username/Password Authentication protocol, the Username/Password 117 subnegotiation begins. This begins with the client producing a 118 Username/Password request: 119 120 +----+------+----------+------+----------+ 121 |VER | ULEN | UNAME | PLEN | PASSWD | 122 +----+------+----------+------+----------+ 123 | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 124 +----+------+----------+------+----------+ 125 126 The VER field contains the current version of the subnegotiation, 127 which is X'01'. The ULEN field contains the length of the UNAME field 128 that follows. The UNAME field contains the username as known to the 129 source operating system. The PLEN field contains the length of the 130 PASSWD field that follows. The PASSWD field contains the password 131 association with the given UNAME. 132*/ 133 index = 0; 134 buf[index++] = 1; 135 buf[index++] = (byte) (user.length()); 136 byte[] userBytes = user.getBytes(StringUtils.UTF8); 137 System.arraycopy(userBytes, 0, buf, index, 138 user.length()); 139 index += user.length(); 140 byte[] passwordBytes = passwd.getBytes(StringUtils.UTF8); 141 buf[index++] = (byte) (passwordBytes.length); 142 System.arraycopy(passwordBytes, 0, buf, index, 143 passwd.length()); 144 index += passwd.length(); 145 146 out.write(buf, 0, index); 147 148/* 149 The server verifies the supplied UNAME and PASSWD, and sends the 150 following response: 151 152 +----+--------+ 153 |VER | STATUS | 154 +----+--------+ 155 | 1 | 1 | 156 +----+--------+ 157 158 A STATUS field of X'00' indicates success. If the server returns a 159 `failure' (STATUS value other than X'00') status, it MUST close the 160 connection. 161*/ 162 fill(in, buf, 2); 163 if (buf[1] == 0) 164 { 165 check = true; 166 } 167 break; 168 default: 169 } 170 171 if (!check) 172 { 173 try 174 { 175 socket.close(); 176 } 177 catch (Exception eee) 178 { 179 } 180 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, 181 "fail in SOCKS5 proxy"); 182 } 183 184/* 185 The SOCKS request is formed as follows: 186 187 +----+-----+-------+------+----------+----------+ 188 |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 189 +----+-----+-------+------+----------+----------+ 190 | 1 | 1 | X'00' | 1 | Variable | 2 | 191 +----+-----+-------+------+----------+----------+ 192 193 Where: 194 195 o VER protocol version: X'05' 196 o CMD 197 o CONNECT X'01' 198 o BIND X'02' 199 o UDP ASSOCIATE X'03' 200 o RSV RESERVED 201 o ATYP address type of following address 202 o IP V4 address: X'01' 203 o DOMAINNAME: X'03' 204 o IP V6 address: X'04' 205 o DST.ADDR desired destination address 206 o DST.PORT desired destination port in network octet 207 order 208*/ 209 210 index = 0; 211 buf[index++] = 5; 212 buf[index++] = 1; // CONNECT 213 buf[index++] = 0; 214 215 byte[] hostb = host.getBytes(StringUtils.UTF8); 216 int len = hostb.length; 217 buf[index++] = 3; // DOMAINNAME 218 buf[index++] = (byte) (len); 219 System.arraycopy(hostb, 0, buf, index, len); 220 index += len; 221 buf[index++] = (byte) (port >>> 8); 222 buf[index++] = (byte) (port & 0xff); 223 224 out.write(buf, 0, index); 225 226/* 227 The SOCKS request information is sent by the client as soon as it has 228 established a connection to the SOCKS server, and completed the 229 authentication negotiations. The server evaluates the request, and 230 returns a reply formed as follows: 231 232 +----+-----+-------+------+----------+----------+ 233 |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 234 +----+-----+-------+------+----------+----------+ 235 | 1 | 1 | X'00' | 1 | Variable | 2 | 236 +----+-----+-------+------+----------+----------+ 237 238 Where: 239 240 o VER protocol version: X'05' 241 o REP Reply field: 242 o X'00' succeeded 243 o X'01' general SOCKS server failure 244 o X'02' connection not allowed by ruleset 245 o X'03' Network unreachable 246 o X'04' Host unreachable 247 o X'05' XMPPConnection refused 248 o X'06' TTL expired 249 o X'07' Command not supported 250 o X'08' Address type not supported 251 o X'09' to X'FF' unassigned 252 o RSV RESERVED 253 o ATYP address type of following address 254 o IP V4 address: X'01' 255 o DOMAINNAME: X'03' 256 o IP V6 address: X'04' 257 o BND.ADDR server bound address 258 o BND.PORT server bound port in network octet order 259*/ 260 261 fill(in, buf, 4); 262 263 if (buf[1] != 0) 264 { 265 try 266 { 267 socket.close(); 268 } 269 catch (Exception eee) 270 { 271 } 272 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, 273 "server returns " + buf[1]); 274 } 275 276 switch (buf[3] & 0xff) 277 { 278 case 1: 279 fill(in, buf, 6); 280 break; 281 case 3: 282 fill(in, buf, 1); 283 fill(in, buf, (buf[0] & 0xff) + 2); 284 break; 285 case 4: 286 fill(in, buf, 18); 287 break; 288 default: 289 } 290 } 291 catch (RuntimeException e) 292 { 293 throw e; 294 } 295 catch (Exception e) 296 { 297 try 298 { 299 socket.close(); 300 } 301 catch (Exception eee) 302 { 303 } 304 // TODO convert to IOException(e) when minimum Android API level is 9 or higher 305 throw new IOException(e.getLocalizedMessage()); 306 } 307 } 308 309 private static void fill(InputStream in, byte[] buf, int len) 310 throws IOException 311 { 312 int s = 0; 313 while (s < len) 314 { 315 int i = in.read(buf, s, len - s); 316 if (i <= 0) 317 { 318 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "stream " + 319 "is closed"); 320 } 321 s += i; 322 } 323 } 324 325}