1/**
2 * $RCSfile$
3 * $Revision$
4 * $Date$
5 *
6 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18package org.jivesoftware.smack.proxy;
19
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.OutputStream;
23import java.net.InetAddress;
24import java.net.Socket;
25import java.net.UnknownHostException;
26import javax.net.SocketFactory;
27
28/**
29 * Socket factory for Socks5 proxy
30 *
31 * @author Atul Aggarwal
32 */
33public class Socks5ProxySocketFactory
34    extends SocketFactory
35{
36    private ProxyInfo proxy;
37
38    public Socks5ProxySocketFactory(ProxyInfo proxy)
39    {
40        this.proxy = proxy;
41    }
42
43    public Socket createSocket(String host, int port)
44        throws IOException, UnknownHostException
45    {
46        return socks5ProxifiedSocket(host,port);
47    }
48
49    public Socket createSocket(String host ,int port, InetAddress localHost,
50                                int localPort)
51        throws IOException, UnknownHostException
52    {
53
54        return socks5ProxifiedSocket(host,port);
55
56    }
57
58    public Socket createSocket(InetAddress host, int port)
59        throws IOException
60    {
61
62        return socks5ProxifiedSocket(host.getHostAddress(),port);
63
64    }
65
66    public Socket createSocket( InetAddress address, int port,
67                                InetAddress localAddress, int localPort)
68        throws IOException
69    {
70
71        return socks5ProxifiedSocket(address.getHostAddress(),port);
72
73    }
74
75    private Socket socks5ProxifiedSocket(String host, int port)
76        throws IOException
77    {
78        Socket socket = null;
79        InputStream in = null;
80        OutputStream out = null;
81        String proxy_host = proxy.getProxyAddress();
82        int proxy_port = proxy.getProxyPort();
83        String user = proxy.getProxyUsername();
84        String passwd = proxy.getProxyPassword();
85
86        try
87        {
88            socket=new Socket(proxy_host, proxy_port);
89            in=socket.getInputStream();
90            out=socket.getOutputStream();
91
92            socket.setTcpNoDelay(true);
93
94            byte[] buf=new byte[1024];
95            int index=0;
96
97/*
98                   +----+----------+----------+
99                   |VER | NMETHODS | METHODS  |
100                   +----+----------+----------+
101                   | 1  |    1     | 1 to 255 |
102                   +----+----------+----------+
103
104   The VER field is set to X'05' for this version of the protocol.  The
105   NMETHODS field contains the number of method identifier octets that
106   appear in the METHODS field.
107
108   The values currently defined for METHOD are:
109
110          o  X'00' NO AUTHENTICATION REQUIRED
111          o  X'01' GSSAPI
112          o  X'02' USERNAME/PASSWORD
113          o  X'03' to X'7F' IANA ASSIGNED
114          o  X'80' to X'FE' RESERVED FOR PRIVATE METHODS
115          o  X'FF' NO ACCEPTABLE METHODS
116*/
117
118            buf[index++]=5;
119
120            buf[index++]=2;
121            buf[index++]=0;           // NO AUTHENTICATION REQUIRED
122            buf[index++]=2;           // USERNAME/PASSWORD
123
124            out.write(buf, 0, index);
125
126/*
127    The server selects from one of the methods given in METHODS, and
128    sends a METHOD selection message:
129
130                         +----+--------+
131                         |VER | METHOD |
132                         +----+--------+
133                         | 1  |   1    |
134                         +----+--------+
135*/
136      //in.read(buf, 0, 2);
137            fill(in, buf, 2);
138
139            boolean check=false;
140            switch((buf[1])&0xff)
141            {
142                case 0:                // NO AUTHENTICATION REQUIRED
143                    check=true;
144                    break;
145                case 2:                // USERNAME/PASSWORD
146                    if(user==null || passwd==null)
147                    {
148                        break;
149                    }
150
151/*
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