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.InetAddress;
023import java.net.Socket;
024import java.net.UnknownHostException;
025import javax.net.SocketFactory;
026
027/**
028 * Socket factory for Socks5 proxy
029 * 
030 * @author Atul Aggarwal
031 */
032public class Socks5ProxySocketFactory 
033    extends SocketFactory
034{
035    private ProxyInfo proxy;
036    
037    public Socks5ProxySocketFactory(ProxyInfo proxy)
038    {
039        this.proxy = proxy;
040    }
041
042    public Socket createSocket(String host, int port) 
043        throws IOException, UnknownHostException
044    {
045        return socks5ProxifiedSocket(host,port);
046    }
047
048    public Socket createSocket(String host ,int port, InetAddress localHost,
049                                int localPort)
050        throws IOException, UnknownHostException
051    {
052        
053        return socks5ProxifiedSocket(host,port);
054        
055    }
056
057    public Socket createSocket(InetAddress host, int port)
058        throws IOException
059    {
060        
061        return socks5ProxifiedSocket(host.getHostAddress(),port);
062        
063    }
064
065    public Socket createSocket( InetAddress address, int port, 
066                                InetAddress localAddress, int localPort) 
067        throws IOException
068    {
069        
070        return socks5ProxifiedSocket(address.getHostAddress(),port);
071        
072    }
073    
074    private Socket socks5ProxifiedSocket(String host, int port) 
075        throws IOException
076    {
077        Socket socket = null;
078        InputStream in = null;
079        OutputStream out = null;
080        String proxy_host = proxy.getProxyAddress();
081        int proxy_port = proxy.getProxyPort();
082        String user = proxy.getProxyUsername();
083        String passwd = proxy.getProxyPassword();
084        
085        try
086        {
087            socket=new Socket(proxy_host, proxy_port);    
088            in=socket.getInputStream();
089            out=socket.getOutputStream();
090
091            socket.setTcpNoDelay(true);
092
093            byte[] buf=new byte[1024];
094            int index=0;
095
096/*
097                   +----+----------+----------+
098                   |VER | NMETHODS | METHODS  |
099                   +----+----------+----------+
100                   | 1  |    1     | 1 to 255 |
101                   +----+----------+----------+
102
103   The VER field is set to X'05' for this version of the protocol.  The
104   NMETHODS field contains the number of method identifier octets that
105   appear in the METHODS field.
106
107   The values currently defined for METHOD are:
108
109          o  X'00' NO AUTHENTICATION REQUIRED
110          o  X'01' GSSAPI
111          o  X'02' USERNAME/PASSWORD
112          o  X'03' to X'7F' IANA ASSIGNED
113          o  X'80' to X'FE' RESERVED FOR PRIVATE METHODS
114          o  X'FF' NO ACCEPTABLE METHODS
115*/
116
117            buf[index++]=5;
118
119            buf[index++]=2;
120            buf[index++]=0;           // NO AUTHENTICATION REQUIRED
121            buf[index++]=2;           // USERNAME/PASSWORD
122
123            out.write(buf, 0, index);
124
125/*
126    The server selects from one of the methods given in METHODS, and
127    sends a METHOD selection message:
128
129                         +----+--------+
130                         |VER | METHOD |
131                         +----+--------+
132                         | 1  |   1    |
133                         +----+--------+
134*/
135      //in.read(buf, 0, 2);
136            fill(in, buf, 2);
137
138            boolean check=false;
139            switch((buf[1])&0xff)
140            {
141                case 0:                // NO AUTHENTICATION REQUIRED
142                    check=true;
143                    break;
144                case 2:                // USERNAME/PASSWORD
145                    if(user==null || passwd==null)
146                    {
147                        break;
148                    }
149
150/*
151   Once the SOCKS V5 server has started, and the client has selected the
152   Username/Password Authentication protocol, the Username/Password
153   subnegotiation begins.  This begins with the client producing a
154   Username/Password request:
155
156           +----+------+----------+------+----------+
157           |VER | ULEN |  UNAME   | PLEN |  PASSWD  |
158           +----+------+----------+------+----------+
159           | 1  |  1   | 1 to 255 |  1   | 1 to 255 |
160           +----+------+----------+------+----------+
161
162   The VER field contains the current version of the subnegotiation,
163   which is X'01'. The ULEN field contains the length of the UNAME field
164   that follows. The UNAME field contains the username as known to the
165   source operating system. The PLEN field contains the length of the
166   PASSWD field that follows. The PASSWD field contains the password
167   association with the given UNAME.
168*/
169                    index=0;
170                    buf[index++]=1;
171                    buf[index++]=(byte)(user.length());
172                    System.arraycopy(user.getBytes(), 0, buf, index, 
173                        user.length());
174                    index+=user.length();
175                    buf[index++]=(byte)(passwd.length());
176                    System.arraycopy(passwd.getBytes(), 0, buf, index, 
177                        passwd.length());
178                    index+=passwd.length();
179
180                    out.write(buf, 0, index);
181
182/*
183   The server verifies the supplied UNAME and PASSWD, and sends the
184   following response:
185
186                        +----+--------+
187                        |VER | STATUS |
188                        +----+--------+
189                        | 1  |   1    |
190                        +----+--------+
191
192   A STATUS field of X'00' indicates success. If the server returns a
193   `failure' (STATUS value other than X'00') status, it MUST close the
194   connection.
195*/
196                    //in.read(buf, 0, 2);
197                    fill(in, buf, 2);
198                    if(buf[1]==0)
199                    {
200                        check=true;
201                    }
202                    break;
203                default:
204            }
205
206            if(!check)
207            {
208                try
209                {
210                    socket.close();
211                }
212                catch(Exception eee)
213                {
214                }
215                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,
216                    "fail in SOCKS5 proxy");
217            }
218
219/*
220      The SOCKS request is formed as follows:
221
222        +----+-----+-------+------+----------+----------+
223        |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
224        +----+-----+-------+------+----------+----------+
225        | 1  |  1  | X'00' |  1   | Variable |    2     |
226        +----+-----+-------+------+----------+----------+
227
228      Where:
229
230      o  VER    protocol version: X'05'
231      o  CMD
232         o  CONNECT X'01'
233         o  BIND X'02'
234         o  UDP ASSOCIATE X'03'
235      o  RSV    RESERVED
236         o  ATYP   address type of following address
237         o  IP V4 address: X'01'
238         o  DOMAINNAME: X'03'
239         o  IP V6 address: X'04'
240      o  DST.ADDR       desired destination address
241      o  DST.PORT desired destination port in network octet
242         order
243*/
244     
245            index=0;
246            buf[index++]=5;
247            buf[index++]=1;       // CONNECT
248            buf[index++]=0;
249
250            byte[] hostb=host.getBytes();
251            int len=hostb.length;
252            buf[index++]=3;      // DOMAINNAME
253            buf[index++]=(byte)(len);
254            System.arraycopy(hostb, 0, buf, index, len);
255            index+=len;
256            buf[index++]=(byte)(port>>>8);
257            buf[index++]=(byte)(port&0xff);
258
259            out.write(buf, 0, index);
260
261/*
262   The SOCKS request information is sent by the client as soon as it has
263   established a connection to the SOCKS server, and completed the
264   authentication negotiations.  The server evaluates the request, and
265   returns a reply formed as follows:
266
267        +----+-----+-------+------+----------+----------+
268        |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
269        +----+-----+-------+------+----------+----------+
270        | 1  |  1  | X'00' |  1   | Variable |    2     |
271        +----+-----+-------+------+----------+----------+
272
273   Where:
274
275   o  VER    protocol version: X'05'
276   o  REP    Reply field:
277      o  X'00' succeeded
278      o  X'01' general SOCKS server failure
279      o  X'02' connection not allowed by ruleset
280      o  X'03' Network unreachable
281      o  X'04' Host unreachable
282      o  X'05' XMPPConnection refused
283      o  X'06' TTL expired
284      o  X'07' Command not supported
285      o  X'08' Address type not supported
286      o  X'09' to X'FF' unassigned
287    o  RSV    RESERVED
288    o  ATYP   address type of following address
289      o  IP V4 address: X'01'
290      o  DOMAINNAME: X'03'
291      o  IP V6 address: X'04'
292    o  BND.ADDR       server bound address
293    o  BND.PORT       server bound port in network octet order
294*/
295
296      //in.read(buf, 0, 4);
297            fill(in, buf, 4);
298
299            if(buf[1]!=0)
300            {
301                try
302                {
303                    socket.close();
304                }
305                catch(Exception eee)
306                {
307                }
308                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, 
309                    "server returns "+buf[1]);
310            }
311
312            switch(buf[3]&0xff)
313            {
314                case 1:
315                    //in.read(buf, 0, 6);
316                    fill(in, buf, 6);
317                    break;
318                case 3:
319                    //in.read(buf, 0, 1);
320                    fill(in, buf, 1);
321                    //in.read(buf, 0, buf[0]+2);
322                    fill(in, buf, (buf[0]&0xff)+2);
323                    break;
324                case 4:
325                    //in.read(buf, 0, 18);
326                    fill(in, buf, 18);
327                    break;
328                default:
329            }
330            return socket;
331            
332        }
333        catch(RuntimeException e)
334        {
335            throw e;
336        }
337        catch(Exception e)
338        {
339            try
340            {
341                if(socket!=null)
342                {
343                    socket.close(); 
344                }
345            }
346            catch(Exception eee)
347            {
348            }
349            String message="ProxySOCKS5: "+e.toString();
350            if(e instanceof Throwable)
351            {
352                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,message, 
353                    (Throwable)e);
354            }
355            throw new IOException(message);
356        }
357    }
358    
359    private void fill(InputStream in, byte[] buf, int len) 
360      throws IOException
361    {
362        int s=0;
363        while(s<len)
364        {
365            int i=in.read(buf, s, len-s);
366            if(i<=0)
367            {
368                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "stream " +
369                    "is closed");
370            }
371            s+=i;
372        }
373    }
374}