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;
025
026import javax.net.SocketFactory;
027
028/**
029 * Socket factory for socks4 proxy 
030 *  
031 * @author Atul Aggarwal
032 */
033public class Socks4ProxySocketFactory 
034    extends SocketFactory
035{
036    private ProxyInfo proxy;
037    
038    public Socks4ProxySocketFactory(ProxyInfo proxy)
039    {
040        this.proxy = proxy;
041    }
042
043    public Socket createSocket(String host, int port) 
044        throws IOException, UnknownHostException
045    {
046        return socks4ProxifiedSocket(host,port);
047        
048    }
049
050    public Socket createSocket(String host ,int port, InetAddress localHost,
051                                int localPort)
052        throws IOException, UnknownHostException
053    {
054        return socks4ProxifiedSocket(host,port);
055    }
056
057    public Socket createSocket(InetAddress host, int port)
058        throws IOException
059    {
060        return socks4ProxifiedSocket(host.getHostAddress(),port);
061    }
062
063    public Socket createSocket( InetAddress address, int port, 
064                                InetAddress localAddress, int localPort) 
065        throws IOException
066    {
067        return socks4ProxifiedSocket(address.getHostAddress(),port);
068        
069    }
070    
071    @SuppressWarnings("resource")
072    private Socket socks4ProxifiedSocket(String host, int port) 
073        throws IOException
074    {
075        Socket socket = null;
076        InputStream in = null;
077        OutputStream out = null;
078        String proxy_host = proxy.getProxyAddress();
079        int proxy_port = proxy.getProxyPort();
080        String user = proxy.getProxyUsername();
081
082        try
083        {
084            socket=new Socket(proxy_host, proxy_port);    
085            in=socket.getInputStream();
086            out=socket.getOutputStream();
087            socket.setTcpNoDelay(true);
088            
089            byte[] buf=new byte[1024];
090            int index=0;
091
092    /*
093    1) CONNECT
094
095    The client connects to the SOCKS server and sends a CONNECT request when
096    it wants to establish a connection to an application server. The client
097    includes in the request packet the IP address and the port number of the
098    destination host, and userid, in the following format.
099
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}