1ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com/**
2ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * $RCSfile$
3ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * $Revision$
4ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * $Date$
5ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com *
6ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
7ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * you may not use this file except in compliance with the License.
88a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com * You may obtain a copy of the License at
98a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com *
108a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com *     http://www.apache.org/licenses/LICENSE-2.0
114991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com *
12c73dd5c6880739f26216f198c757028fd28df1a4djsollen@google.com * Unless required by applicable law or agreed to in writing, software
138a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com * distributed under the License is distributed on an "AS IS" BASIS,
144991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
154991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com * See the License for the specific language governing permissions and
164991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com * limitations under the License.
178a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com */
188a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.compackage org.jivesoftware.smack.proxy;
19038aff623d9fd47946cd31685f74cf473f7c84f0senorblanco@chromium.org
20a728e35edcffd99216e3965a4b908ad0df7f69c2vandebo@chromium.orgimport java.io.IOException;
214e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.comimport java.io.InputStream;
22038aff623d9fd47946cd31685f74cf473f7c84f0senorblanco@chromium.orgimport java.io.OutputStream;
234e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.comimport java.net.InetAddress;
2482065d667f64e232bcde2ad849756a6096fcbe6freed@google.comimport java.net.Socket;
2582065d667f64e232bcde2ad849756a6096fcbe6freed@google.comimport java.net.UnknownHostException;
26038aff623d9fd47946cd31685f74cf473f7c84f0senorblanco@chromium.orgimport javax.net.SocketFactory;
27038aff623d9fd47946cd31685f74cf473f7c84f0senorblanco@chromium.org
284868e6b221a4a98e40f977851af5fcf09631ea15senorblanco@chromium.org/**
29fbfcd5602128ec010c82cb733c9cdc0a3254f9f3rmistry@google.com * Socket factory for Socks5 proxy
304868e6b221a4a98e40f977851af5fcf09631ea15senorblanco@chromium.org *
314868e6b221a4a98e40f977851af5fcf09631ea15senorblanco@chromium.org * @author Atul Aggarwal
328a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com */
33fbfcd5602128ec010c82cb733c9cdc0a3254f9f3rmistry@google.compublic class Socks5ProxySocketFactory
34038aff623d9fd47946cd31685f74cf473f7c84f0senorblanco@chromium.org    extends SocketFactory
354e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com{
368a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com    private ProxyInfo proxy;
374868e6b221a4a98e40f977851af5fcf09631ea15senorblanco@chromium.org
384868e6b221a4a98e40f977851af5fcf09631ea15senorblanco@chromium.org    public Socks5ProxySocketFactory(ProxyInfo proxy)
394e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com    {
40a6398911174d5445456ecb2f5f4f0565db2f100bjunov@google.com        this.proxy = proxy;
41a6398911174d5445456ecb2f5f4f0565db2f100bjunov@google.com    }
42a6398911174d5445456ecb2f5f4f0565db2f100bjunov@google.com
434868e6b221a4a98e40f977851af5fcf09631ea15senorblanco@chromium.org    public Socket createSocket(String host, int port)
444e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com        throws IOException, UnknownHostException
454e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com    {
464e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com        return socks5ProxifiedSocket(host,port);
474868e6b221a4a98e40f977851af5fcf09631ea15senorblanco@chromium.org    }
484868e6b221a4a98e40f977851af5fcf09631ea15senorblanco@chromium.org
498a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com    public Socket createSocket(String host ,int port, InetAddress localHost,
508a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com                                int localPort)
516bac947cd5bc460dd9166ada6310d678fd2e39f8reed@google.com        throws IOException, UnknownHostException
526bac947cd5bc460dd9166ada6310d678fd2e39f8reed@google.com    {
536bac947cd5bc460dd9166ada6310d678fd2e39f8reed@google.com
548a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com        return socks5ProxifiedSocket(host,port);
558a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
56c73dd5c6880739f26216f198c757028fd28df1a4djsollen@google.com    }
57c73dd5c6880739f26216f198c757028fd28df1a4djsollen@google.com
58c73dd5c6880739f26216f198c757028fd28df1a4djsollen@google.com    public Socket createSocket(InetAddress host, int port)
59c73dd5c6880739f26216f198c757028fd28df1a4djsollen@google.com        throws IOException
608a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com    {
618a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
624e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com        return socks5ProxifiedSocket(host.getHostAddress(),port);
6382065d667f64e232bcde2ad849756a6096fcbe6freed@google.com
644868e6b221a4a98e40f977851af5fcf09631ea15senorblanco@chromium.org    }
658a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
668a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com    public Socket createSocket( InetAddress address, int port,
6754924243c1b65b3ee6d8fa064b50a9b1bb2a19a5djsollen@google.com                                InetAddress localAddress, int localPort)
6854924243c1b65b3ee6d8fa064b50a9b1bb2a19a5djsollen@google.com        throws IOException
698a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com    {
708a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
71c73dd5c6880739f26216f198c757028fd28df1a4djsollen@google.com        return socks5ProxifiedSocket(address.getHostAddress(),port);
728a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
734868e6b221a4a98e40f977851af5fcf09631ea15senorblanco@chromium.org    }
74c73dd5c6880739f26216f198c757028fd28df1a4djsollen@google.com
758a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com    private Socket socks5ProxifiedSocket(String host, int port)
768a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com        throws IOException
774e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com    {
784e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com        Socket socket = null;
798a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com        InputStream in = null;
808a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com        OutputStream out = null;
814e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com        String proxy_host = proxy.getProxyAddress();
828a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com        int proxy_port = proxy.getProxyPort();
834e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com        String user = proxy.getProxyUsername();
844e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com        String passwd = proxy.getProxyPassword();
854e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com
864e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com        try
874e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com        {
884e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com            socket=new Socket(proxy_host, proxy_port);
8956c69773aea56c6c6bd47bc7e7970dd081205184djsollen@google.com            in=socket.getInputStream();
90f5dbe2f00f853c6a1719924bdd0c33335a53423adjsollen@google.com            out=socket.getOutputStream();
91f5dbe2f00f853c6a1719924bdd0c33335a53423adjsollen@google.com
92f5dbe2f00f853c6a1719924bdd0c33335a53423adjsollen@google.com            socket.setTcpNoDelay(true);
93f5dbe2f00f853c6a1719924bdd0c33335a53423adjsollen@google.com
94f5dbe2f00f853c6a1719924bdd0c33335a53423adjsollen@google.com            byte[] buf=new byte[1024];
95f5dbe2f00f853c6a1719924bdd0c33335a53423adjsollen@google.com            int index=0;
96f5dbe2f00f853c6a1719924bdd0c33335a53423adjsollen@google.com
974e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com/*
98f5dbe2f00f853c6a1719924bdd0c33335a53423adjsollen@google.com                   +----+----------+----------+
994e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com                   |VER | NMETHODS | METHODS  |
1004e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com                   +----+----------+----------+
1014e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com                   | 1  |    1     | 1 to 255 |
1024e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com                   +----+----------+----------+
1034e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com
1044e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com   The VER field is set to X'05' for this version of the protocol.  The
1054e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com   NMETHODS field contains the number of method identifier octets that
1064e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com   appear in the METHODS field.
1074e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com
1084e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com   The values currently defined for METHOD are:
1094e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com
1104e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com          o  X'00' NO AUTHENTICATION REQUIRED
1114e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com          o  X'01' GSSAPI
1124e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com          o  X'02' USERNAME/PASSWORD
1134e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com          o  X'03' to X'7F' IANA ASSIGNED
1144e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com          o  X'80' to X'FE' RESERVED FOR PRIVATE METHODS
1154e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com          o  X'FF' NO ACCEPTABLE METHODS
1164e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com*/
1174e2b3d3fb1288c6dc0f3ea1c0aa4a0d7c603bd7breed@google.com
1188a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com            buf[index++]=5;
1198a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1204991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com            buf[index++]=2;
1214991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com            buf[index++]=0;           // NO AUTHENTICATION REQUIRED
1224991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com            buf[index++]=2;           // USERNAME/PASSWORD
1234991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com
1244991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com            out.write(buf, 0, index);
1254991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com
1264991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com/*
1274991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com    The server selects from one of the methods given in METHODS, and
1284991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com    sends a METHOD selection message:
1294991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com
1304991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                         +----+--------+
1314991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                         |VER | METHOD |
1324991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                         +----+--------+
1334991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                         | 1  |   1    |
1344991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                         +----+--------+
1354991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com*/
1364991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com      //in.read(buf, 0, 2);
1374991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com            fill(in, buf, 2);
1384991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com
1394991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com            boolean check=false;
1404991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com            switch((buf[1])&0xff)
1414991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com            {
1424991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                case 0:                // NO AUTHENTICATION REQUIRED
1434991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                    check=true;
1444991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                    break;
1454991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                case 2:                // USERNAME/PASSWORD
1464991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                    if(user==null || passwd==null)
1474991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                    {
1484991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                        break;
1494991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com                    }
1504991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com
1514991b8f23482afc1494fd17647421ce68de53331robertphillips@google.com/*
152   Once the SOCKS V5 server has started, and the client has selected the
153   Username/Password Authentication protocol, the Username/Password
154   subnegotiation begins.  This begins with the client producing a
155   Username/Password request:
156
157           +----+------+----------+------+----------+
158           |VER | ULEN |  UNAME   | PLEN |  PASSWD  |
159           +----+------+----------+------+----------+
160           | 1  |  1   | 1 to 255 |  1   | 1 to 255 |
161           +----+------+----------+------+----------+
162
163   The VER field contains the current version of the subnegotiation,
164   which is X'01'. The ULEN field contains the length of the UNAME field
165   that follows. The UNAME field contains the username as known to the
166   source operating system. The PLEN field contains the length of the
167   PASSWD field that follows. The PASSWD field contains the password
168   association with the given UNAME.
169*/
170                    index=0;
171                    buf[index++]=1;
172                    buf[index++]=(byte)(user.length());
173                    System.arraycopy(user.getBytes(), 0, buf, index,
174                        user.length());
175                    index+=user.length();
176                    buf[index++]=(byte)(passwd.length());
177                    System.arraycopy(passwd.getBytes(), 0, buf, index,
178                        passwd.length());
179                    index+=passwd.length();
180
181                    out.write(buf, 0, index);
182
183/*
184   The server verifies the supplied UNAME and PASSWD, and sends the
185   following response:
186
187                        +----+--------+
188                        |VER | STATUS |
189                        +----+--------+
190                        | 1  |   1    |
191                        +----+--------+
192
193   A STATUS field of X'00' indicates success. If the server returns a
194   `failure' (STATUS value other than X'00') status, it MUST close the
195   connection.
196*/
197                    //in.read(buf, 0, 2);
198                    fill(in, buf, 2);
199                    if(buf[1]==0)
200                    {
201                        check=true;
202                    }
203                    break;
204                default:
205            }
206
207            if(!check)
208            {
209                try
210                {
211                    socket.close();
212                }
213                catch(Exception eee)
214                {
215                }
216                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,
217                    "fail in SOCKS5 proxy");
218            }
219
220/*
221      The SOCKS request is formed as follows:
222
223        +----+-----+-------+------+----------+----------+
224        |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
225        +----+-----+-------+------+----------+----------+
226        | 1  |  1  | X'00' |  1   | Variable |    2     |
227        +----+-----+-------+------+----------+----------+
228
229      Where:
230
231      o  VER    protocol version: X'05'
232      o  CMD
233         o  CONNECT X'01'
234         o  BIND X'02'
235         o  UDP ASSOCIATE X'03'
236      o  RSV    RESERVED
237         o  ATYP   address type of following address
238         o  IP V4 address: X'01'
239         o  DOMAINNAME: X'03'
240         o  IP V6 address: X'04'
241      o  DST.ADDR       desired destination address
242      o  DST.PORT desired destination port in network octet
243         order
244*/
245
246            index=0;
247            buf[index++]=5;
248            buf[index++]=1;       // CONNECT
249            buf[index++]=0;
250
251            byte[] hostb=host.getBytes();
252            int len=hostb.length;
253            buf[index++]=3;      // DOMAINNAME
254            buf[index++]=(byte)(len);
255            System.arraycopy(hostb, 0, buf, index, len);
256            index+=len;
257            buf[index++]=(byte)(port>>>8);
258            buf[index++]=(byte)(port&0xff);
259
260            out.write(buf, 0, index);
261
262/*
263   The SOCKS request information is sent by the client as soon as it has
264   established a connection to the SOCKS server, and completed the
265   authentication negotiations.  The server evaluates the request, and
266   returns a reply formed as follows:
267
268        +----+-----+-------+------+----------+----------+
269        |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
270        +----+-----+-------+------+----------+----------+
271        | 1  |  1  | X'00' |  1   | Variable |    2     |
272        +----+-----+-------+------+----------+----------+
273
274   Where:
275
276   o  VER    protocol version: X'05'
277   o  REP    Reply field:
278      o  X'00' succeeded
279      o  X'01' general SOCKS server failure
280      o  X'02' connection not allowed by ruleset
281      o  X'03' Network unreachable
282      o  X'04' Host unreachable
283      o  X'05' Connection refused
284      o  X'06' TTL expired
285      o  X'07' Command not supported
286      o  X'08' Address type not supported
287      o  X'09' to X'FF' unassigned
288    o  RSV    RESERVED
289    o  ATYP   address type of following address
290      o  IP V4 address: X'01'
291      o  DOMAINNAME: X'03'
292      o  IP V6 address: X'04'
293    o  BND.ADDR       server bound address
294    o  BND.PORT       server bound port in network octet order
295*/
296
297      //in.read(buf, 0, 4);
298            fill(in, buf, 4);
299
300            if(buf[1]!=0)
301            {
302                try
303                {
304                    socket.close();
305                }
306                catch(Exception eee)
307                {
308                }
309                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,
310                    "server returns "+buf[1]);
311            }
312
313            switch(buf[3]&0xff)
314            {
315                case 1:
316                    //in.read(buf, 0, 6);
317                    fill(in, buf, 6);
318                    break;
319                case 3:
320                    //in.read(buf, 0, 1);
321                    fill(in, buf, 1);
322                    //in.read(buf, 0, buf[0]+2);
323                    fill(in, buf, (buf[0]&0xff)+2);
324                    break;
325                case 4:
326                    //in.read(buf, 0, 18);
327                    fill(in, buf, 18);
328                    break;
329                default:
330            }
331            return socket;
332
333        }
334        catch(RuntimeException e)
335        {
336            throw e;
337        }
338        catch(Exception e)
339        {
340            try
341            {
342                if(socket!=null)
343                {
344                    socket.close();
345                }
346            }
347            catch(Exception eee)
348            {
349            }
350            String message="ProxySOCKS5: "+e.toString();
351            if(e instanceof Throwable)
352            {
353                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,message,
354                    (Throwable)e);
355            }
356            throw new IOException(message);
357        }
358    }
359
360    private void fill(InputStream in, byte[] buf, int len)
361      throws IOException
362    {
363        int s=0;
364        while(s<len)
365        {
366            int i=in.read(buf, s, len-s);
367            if(i<=0)
368            {
369                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "stream " +
370                    "is closed");
371            }
372            s+=i;
373        }
374    }
375}
376