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