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}