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      //in.read(buf, 0, 2);
101            fill(in, buf, 2);
102
103            boolean check = false;
104            switch ((buf[1]) & 0xff)
105            {
106                case 0:                // NO AUTHENTICATION REQUIRED
107                    check = true;
108                    break;
109                case 2:                // USERNAME/PASSWORD
110                    if (user == null || passwd == null)
111                    {
112                        break;
113                    }
114
115/*
116   Once the SOCKS V5 server has started, and the client has selected the
117   Username/Password Authentication protocol, the Username/Password
118   subnegotiation begins.  This begins with the client producing a
119   Username/Password request:
120
121           +----+------+----------+------+----------+
122           |VER | ULEN |  UNAME   | PLEN |  PASSWD  |
123           +----+------+----------+------+----------+
124           | 1  |  1   | 1 to 255 |  1   | 1 to 255 |
125           +----+------+----------+------+----------+
126
127   The VER field contains the current version of the subnegotiation,
128   which is X'01'. The ULEN field contains the length of the UNAME field
129   that follows. The UNAME field contains the username as known to the
130   source operating system. The PLEN field contains the length of the
131   PASSWD field that follows. The PASSWD field contains the password
132   association with the given UNAME.
133*/
134                    index = 0;
135                    buf[index++] = 1;
136                    buf[index++] = (byte) (user.length());
137                    byte[] userBytes = user.getBytes(StringUtils.UTF8);
138                    System.arraycopy(userBytes, 0, buf, index, 
139                        user.length());
140                    index += user.length();
141                    byte[] passwordBytes = user.getBytes(StringUtils.UTF8);
142                    buf[index++] = (byte) (passwordBytes.length);
143                    System.arraycopy(passwordBytes, 0, buf, index, 
144                        passwd.length());
145                    index += passwd.length();
146
147                    out.write(buf, 0, index);
148
149/*
150   The server verifies the supplied UNAME and PASSWD, and sends the
151   following response:
152
153                        +----+--------+
154                        |VER | STATUS |
155                        +----+--------+
156                        | 1  |   1    |
157                        +----+--------+
158
159   A STATUS field of X'00' indicates success. If the server returns a
160   `failure' (STATUS value other than X'00') status, it MUST close the
161   connection.
162*/
163                    //in.read(buf, 0, 2);
164                    fill(in, buf, 2);
165                    if (buf[1] == 0)
166                    {
167                        check = true;
168                    }
169                    break;
170                default:
171            }
172
173            if (!check)
174            {
175                try
176                {
177                    socket.close();
178                }
179                catch (Exception eee)
180                {
181                }
182                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,
183                    "fail in SOCKS5 proxy");
184            }
185
186/*
187      The SOCKS request is formed as follows:
188
189        +----+-----+-------+------+----------+----------+
190        |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
191        +----+-----+-------+------+----------+----------+
192        | 1  |  1  | X'00' |  1   | Variable |    2     |
193        +----+-----+-------+------+----------+----------+
194
195      Where:
196
197      o  VER    protocol version: X'05'
198      o  CMD
199         o  CONNECT X'01'
200         o  BIND X'02'
201         o  UDP ASSOCIATE X'03'
202      o  RSV    RESERVED
203         o  ATYP   address type of following address
204         o  IP V4 address: X'01'
205         o  DOMAINNAME: X'03'
206         o  IP V6 address: X'04'
207      o  DST.ADDR       desired destination address
208      o  DST.PORT desired destination port in network octet
209         order
210*/
211
212            index = 0;
213            buf[index++] = 5;
214            buf[index++] = 1;       // CONNECT
215            buf[index++] = 0;
216
217            byte[] hostb = host.getBytes(StringUtils.UTF8);
218            int len = hostb.length;
219            buf[index++] = 3;      // DOMAINNAME
220            buf[index++] = (byte) (len);
221            System.arraycopy(hostb, 0, buf, index, len);
222            index += len;
223            buf[index++] = (byte) (port >>> 8);
224            buf[index++] = (byte) (port & 0xff);
225
226            out.write(buf, 0, index);
227
228/*
229   The SOCKS request information is sent by the client as soon as it has
230   established a connection to the SOCKS server, and completed the
231   authentication negotiations.  The server evaluates the request, and
232   returns a reply formed as follows:
233
234        +----+-----+-------+------+----------+----------+
235        |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
236        +----+-----+-------+------+----------+----------+
237        | 1  |  1  | X'00' |  1   | Variable |    2     |
238        +----+-----+-------+------+----------+----------+
239
240   Where:
241
242   o  VER    protocol version: X'05'
243   o  REP    Reply field:
244      o  X'00' succeeded
245      o  X'01' general SOCKS server failure
246      o  X'02' connection not allowed by ruleset
247      o  X'03' Network unreachable
248      o  X'04' Host unreachable
249      o  X'05' XMPPConnection refused
250      o  X'06' TTL expired
251      o  X'07' Command not supported
252      o  X'08' Address type not supported
253      o  X'09' to X'FF' unassigned
254    o  RSV    RESERVED
255    o  ATYP   address type of following address
256      o  IP V4 address: X'01'
257      o  DOMAINNAME: X'03'
258      o  IP V6 address: X'04'
259    o  BND.ADDR       server bound address
260    o  BND.PORT       server bound port in network octet order
261*/
262
263      //in.read(buf, 0, 4);
264            fill(in, buf, 4);
265
266            if (buf[1] != 0)
267            {
268                try
269                {
270                    socket.close();
271                }
272                catch (Exception eee)
273                {
274                }
275                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, 
276                    "server returns " + buf[1]);
277            }
278
279            switch (buf[3] & 0xff)
280            {
281                case 1:
282                    //in.read(buf, 0, 6);
283                    fill(in, buf, 6);
284                    break;
285                case 3:
286                    //in.read(buf, 0, 1);
287                    fill(in, buf, 1);
288                    //in.read(buf, 0, buf[0]+2);
289                    fill(in, buf, (buf[0] & 0xff) + 2);
290                    break;
291                case 4:
292                    //in.read(buf, 0, 18);
293                    fill(in, buf, 18);
294                    break;
295                default:
296            }
297        }
298        catch (RuntimeException e)
299        {
300            throw e;
301        }
302        catch (Exception e)
303        {
304            try
305            {
306                socket.close();
307            }
308            catch (Exception eee)
309            {
310            }
311            // TODO convert to IOException(e) when minimum Android API level is 9 or higher
312            throw new IOException(e.getLocalizedMessage());
313        }
314    }
315
316    private static void fill(InputStream in, byte[] buf, int len) 
317      throws IOException
318    {
319        int s = 0;
320        while (s < len)
321        {
322            int i = in.read(buf, s, len - s);
323            if (i <= 0)
324            {
325                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "stream " +
326                    "is closed");
327            }
328            s += i;
329        }
330    }
331
332}