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}