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.ByteArrayOutputStream; 020import java.io.DataInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStream; 024import java.net.InetSocketAddress; 025import java.net.Socket; 026import java.nio.charset.StandardCharsets; 027 028import org.jivesoftware.smack.util.OutputStreamUtil; 029 030/** 031 * Socket factory for Socks5 proxy. 032 * 033 * @author Atul Aggarwal 034 */ 035public class Socks5ProxySocketConnection implements ProxySocketConnection { 036 037 private final ProxyInfo proxy; 038 039 Socks5ProxySocketConnection(ProxyInfo proxy) { 040 this.proxy = proxy; 041 } 042 043 @Override 044 public void connect(Socket socket, String host, int port, int timeout) 045 throws IOException { 046 String proxy_host = proxy.getProxyAddress(); 047 int proxy_port = proxy.getProxyPort(); 048 String user = proxy.getProxyUsername(); 049 String passwd = proxy.getProxyPassword(); 050 051 socket.connect(new InetSocketAddress(proxy_host, proxy_port), timeout); 052 InputStream in = socket.getInputStream(); 053 DataInputStream dis = new DataInputStream(in); 054 OutputStream out = socket.getOutputStream(); 055 056 ByteArrayOutputStream outBuf = new ByteArrayOutputStream(); 057 byte[] inBuf; 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 outBuf.write(5); 081 082 outBuf.write(2); 083 outBuf.write(0); // NO AUTHENTICATION REQUIRED 084 outBuf.write(2); // USERNAME/PASSWORD 085 086 OutputStreamUtil.writeResetAndFlush(outBuf, out); 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 inBuf = new byte[2]; 099 dis.readFully(inBuf); 100 101 boolean check = false; 102 switch (inBuf[1] & 0xff) { 103 case 0: // NO AUTHENTICATION REQUIRED 104 check = true; 105 break; 106 case 2: // USERNAME/PASSWORD 107 if (user == null || passwd == null) { 108 break; 109 } 110 111/* 112 Once the SOCKS V5 server has started, and the client has selected the 113 Username/Password Authentication protocol, the Username/Password 114 subnegotiation begins. This begins with the client producing a 115 Username/Password request: 116 117 +----+------+----------+------+----------+ 118 |VER | ULEN | UNAME | PLEN | PASSWD | 119 +----+------+----------+------+----------+ 120 | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 121 +----+------+----------+------+----------+ 122 123 The VER field contains the current version of the subnegotiation, 124 which is X'01'. The ULEN field contains the length of the UNAME field 125 that follows. The UNAME field contains the username as known to the 126 source operating system. The PLEN field contains the length of the 127 PASSWD field that follows. The PASSWD field contains the password 128 association with the given UNAME. 129*/ 130 outBuf.write(1); 131 byte[] userBytes = user.getBytes(StandardCharsets.UTF_8); 132 OutputStreamUtil.writeByteSafe(outBuf, userBytes.length, "Username to long"); 133 outBuf.write(userBytes); 134 135 byte[] passwordBytes = passwd.getBytes(StandardCharsets.UTF_8); 136 OutputStreamUtil.writeByteSafe(outBuf, passwordBytes.length, "Password to long"); 137 outBuf.write(passwordBytes); 138 139 OutputStreamUtil.writeResetAndFlush(outBuf, out); 140 141/* 142 The server verifies the supplied UNAME and PASSWD, and sends the 143 following response: 144 145 +----+--------+ 146 |VER | STATUS | 147 +----+--------+ 148 | 1 | 1 | 149 +----+--------+ 150 151 A STATUS field of X'00' indicates success. If the server returns a 152 `failure' (STATUS value other than X'00') status, it MUST close the 153 connection. 154*/ 155 inBuf = new byte[2]; 156 dis.readFully(inBuf); 157 if (inBuf[1] == 0) { 158 check = true; 159 } 160 break; 161 default: 162 } 163 164 if (!check) { 165 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, 166 "fail in SOCKS5 proxy"); 167 } 168 169/* 170 The SOCKS request is formed as follows: 171 172 +----+-----+-------+------+----------+----------+ 173 |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 174 +----+-----+-------+------+----------+----------+ 175 | 1 | 1 | X'00' | 1 | Variable | 2 | 176 +----+-----+-------+------+----------+----------+ 177 178 Where: 179 180 o VER protocol version: X'05' 181 o CMD 182 o CONNECT X'01' 183 o BIND X'02' 184 o UDP ASSOCIATE X'03' 185 o RSV RESERVED 186 o ATYP address type of following address 187 o IP V4 address: X'01' 188 o DOMAINNAME: X'03' 189 o IP V6 address: X'04' 190 o DST.ADDR desired destination address 191 o DST.PORT desired destination port in network octet 192 order 193*/ 194 195 outBuf.write(5); 196 outBuf.write(1); // CONNECT 197 outBuf.write(0); 198 199 byte[] hostb = host.getBytes(StandardCharsets.UTF_8); 200 int len = hostb.length; 201 outBuf.write(3); // DOMAINNAME 202 OutputStreamUtil.writeByteSafe(outBuf, len, "Hostname too long"); 203 outBuf.write(hostb); 204 outBuf.write(port >>> 8); 205 outBuf.write(port & 0xff); 206 207 OutputStreamUtil.writeResetAndFlush(outBuf, out); 208 209/* 210 The SOCKS request information is sent by the client as soon as it has 211 established a connection to the SOCKS server, and completed the 212 authentication negotiations. The server evaluates the request, and 213 returns a reply formed as follows: 214 215 +----+-----+-------+------+----------+----------+ 216 |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 217 +----+-----+-------+------+----------+----------+ 218 | 1 | 1 | X'00' | 1 | Variable | 2 | 219 +----+-----+-------+------+----------+----------+ 220 221 Where: 222 223 o VER protocol version: X'05' 224 o REP Reply field: 225 o X'00' succeeded 226 o X'01' general SOCKS server failure 227 o X'02' connection not allowed by ruleset 228 o X'03' Network unreachable 229 o X'04' Host unreachable 230 o X'05' XMPPConnection refused 231 o X'06' TTL expired 232 o X'07' Command not supported 233 o X'08' Address type not supported 234 o X'09' to X'FF' unassigned 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 BND.ADDR server bound address 241 o BND.PORT server bound port in network octet order 242*/ 243 244 inBuf = new byte[4]; 245 dis.readFully(inBuf); 246 247 if (inBuf[1] != 0) { 248 throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, 249 "server returns " + inBuf[1]); 250 } 251 252 final int addressBytes; 253 // TODO: Use Byte.toUnsignedInt() once Smack's minimum Android SDK level is 26 or higher. 254 final int atyp = inBuf[3] & 0xff; 255 switch (atyp) { 256 case 1: 257 addressBytes = 4; 258 break; 259 case 3: 260 byte domainnameLengthByte = dis.readByte(); 261 // TODO: Use Byte.toUnsignedInt() once Smack's minimum Android SDK level is 26 or higher. 262 addressBytes = domainnameLengthByte & 0xff; 263 break; 264 case 4: 265 addressBytes = 16; 266 break; 267 default: 268 throw new IOException("Unknown ATYP value: " + atyp); 269 } 270 inBuf = new byte[addressBytes + 2]; 271 dis.readFully(inBuf); 272 } 273}