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 socks4 proxy
30 *
31 * @author Atul Aggarwal
32 */
33public class Socks4ProxySocketFactory
34    extends SocketFactory
35{
36    private ProxyInfo proxy;
37
38    public Socks4ProxySocketFactory(ProxyInfo proxy)
39    {
40        this.proxy = proxy;
41    }
42
43    public Socket createSocket(String host, int port)
44        throws IOException, UnknownHostException
45    {
46        return socks4ProxifiedSocket(host,port);
47
48    }
49
50    public Socket createSocket(String host ,int port, InetAddress localHost,
51                                int localPort)
52        throws IOException, UnknownHostException
53    {
54        return socks4ProxifiedSocket(host,port);
55    }
56
57    public Socket createSocket(InetAddress host, int port)
58        throws IOException
59    {
60        return socks4ProxifiedSocket(host.getHostAddress(),port);
61    }
62
63    public Socket createSocket( InetAddress address, int port,
64                                InetAddress localAddress, int localPort)
65        throws IOException
66    {
67        return socks4ProxifiedSocket(address.getHostAddress(),port);
68
69    }
70
71    private Socket socks4ProxifiedSocket(String host, int port)
72        throws IOException
73    {
74        Socket socket = null;
75        InputStream in = null;
76        OutputStream out = null;
77        String proxy_host = proxy.getProxyAddress();
78        int proxy_port = proxy.getProxyPort();
79        String user = proxy.getProxyUsername();
80        String passwd = proxy.getProxyPassword();
81
82        try
83        {
84            socket=new Socket(proxy_host, proxy_port);
85            in=socket.getInputStream();
86            out=socket.getOutputStream();
87            socket.setTcpNoDelay(true);
88
89            byte[] buf=new byte[1024];
90            int index=0;
91
92    /*
93    1) CONNECT
94
95    The client connects to the SOCKS server and sends a CONNECT request when
96    it wants to establish a connection to an application server. The client
97    includes in the request packet the IP address and the port number of the
98    destination host, and userid, in the following format.
99
100           +----+----+----+----+----+----+----+----+----+----+....+----+
101           | VN | CD | DSTPORT |      DSTIP        | USERID       |NULL|
102           +----+----+----+----+----+----+----+----+----+----+....+----+
103    # of bytes:   1    1      2              4           variable       1
104
105    VN is the SOCKS protocol version number and should be 4. CD is the
106    SOCKS command code and should be 1 for CONNECT request. NULL is a byte
107    of all zero bits.
108    */
109
110            index=0;
111            buf[index++]=4;
112            buf[index++]=1;
113
114            buf[index++]=(byte)(port>>>8);
115            buf[index++]=(byte)(port&0xff);
116
117            try
118            {
119                InetAddress addr=InetAddress.getByName(host);
120                byte[] byteAddress = addr.getAddress();
121                for (int i = 0; i < byteAddress.length; i++)
122                {
123                    buf[index++]=byteAddress[i];
124                }
125            }
126            catch(UnknownHostException uhe)
127            {
128                throw new ProxyException(ProxyInfo.ProxyType.SOCKS4,
129                    uhe.toString(), uhe);
130            }
131
132            if(user!=null)
133            {
134                System.arraycopy(user.getBytes(), 0, buf, index, user.length());
135                index+=user.length();
136            }
137            buf[index++]=0;
138            out.write(buf, 0, index);
139
140    /*
141    The SOCKS server checks to see whether such a request should be granted
142    based on any combination of source IP address, destination IP address,
143    destination port number, the userid, and information it may obtain by
144    consulting IDENT, cf. RFC 1413.  If the request is granted, the SOCKS
145    server makes a connection to the specified port of the destination host.
146    A reply packet is sent to the client when this connection is established,
147    or when the request is rejected or the operation fails.
148
149           +----+----+----+----+----+----+----+----+
150           | VN | CD | DSTPORT |      DSTIP        |
151           +----+----+----+----+----+----+----+----+
152    # of bytes:   1    1      2              4
153
154    VN is the version of the reply code and should be 0. CD is the result
155    code with one of the following values:
156
157    90: request granted
158    91: request rejected or failed
159    92: request rejected becasue SOCKS server cannot connect to
160    identd on the client
161    93: request rejected because the client program and identd
162    report different user-ids
163
164    The remaining fields are ignored.
165    */
166
167            int len=6;
168            int s=0;
169            while(s<len)
170            {
171                int i=in.read(buf, s, len-s);
172                if(i<=0)
173                {
174                    throw new ProxyException(ProxyInfo.ProxyType.SOCKS4,
175                        "stream is closed");
176                }
177                s+=i;
178            }
179            if(buf[0]!=0)
180            {
181                throw new ProxyException(ProxyInfo.ProxyType.SOCKS4,
182                    "server returns VN "+buf[0]);
183            }
184            if(buf[1]!=90)
185            {
186                try
187                {
188                    socket.close();
189                }
190                catch(Exception eee)
191                {
192                }
193                String message="ProxySOCKS4: server returns CD "+buf[1];
194                throw new ProxyException(ProxyInfo.ProxyType.SOCKS4,message);
195            }
196            byte[] temp = new byte[2];
197            in.read(temp, 0, 2);
198            return socket;
199        }
200        catch(RuntimeException e)
201        {
202            throw e;
203        }
204        catch(Exception e)
205        {
206            try
207            {
208                if(socket!=null)socket.close();
209            }
210            catch(Exception eee)
211            {
212            }
213            throw new ProxyException(ProxyInfo.ProxyType.SOCKS4, e.toString());
214        }
215    }
216}
217