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}