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.InetSocketAddress;
024import java.net.Socket;
025
026import org.jivesoftware.smack.util.StringUtils;
027
028/**
029 * Socket factory for socks4 proxy.
030 *  
031 * @author Atul Aggarwal
032 */
033public class Socks4ProxySocketConnection implements ProxySocketConnection {
034    private final ProxyInfo proxy;
035
036    Socks4ProxySocketConnection(ProxyInfo proxy)
037    {
038        this.proxy = proxy;
039    }
040
041    @Override
042    public void connect(Socket socket, String host, int port, int timeout)
043                    throws IOException {
044        InputStream in = null;
045        OutputStream out = null;
046        String proxy_host = proxy.getProxyAddress();
047        int proxy_port = proxy.getProxyPort();
048        String user = proxy.getProxyUsername();
049
050        try
051        {
052            socket.connect(new InetSocketAddress(proxy_host, proxy_port), timeout);
053            in = socket.getInputStream();
054            out = socket.getOutputStream();
055            socket.setTcpNoDelay(true);
056
057            byte[] buf = new byte[1024];
058            int index = 0;
059
060    /*
061    1) CONNECT
062
063    The client connects to the SOCKS server and sends a CONNECT request when
064    it wants to establish a connection to an application server. The client
065    includes in the request packet the IP address and the port number of the
066    destination host, and userid, in the following format.
067
068           +----+----+----+----+----+----+----+----+----+----+....+----+
069           | VN | CD | DSTPORT |      DSTIP        | USERID       |NULL|
070           +----+----+----+----+----+----+----+----+----+----+....+----+
071    # of bytes:   1    1      2              4           variable       1
072
073    VN is the SOCKS protocol version number and should be 4. CD is the
074    SOCKS command code and should be 1 for CONNECT request. NULL is a byte
075    of all zero bits.
076    */
077
078            index = 0;
079            buf[index++] = 4;
080            buf[index++] = 1;
081
082            buf[index++] = (byte) (port >>> 8);
083            buf[index++] = (byte) (port & 0xff);
084
085            InetAddress inetAddress = InetAddress.getByName(proxy_host);
086            byte[] byteAddress = inetAddress.getAddress();
087            for (int i = 0; i < byteAddress.length; i++)
088            {
089                buf[index++] = byteAddress[i];
090            }
091
092            if (user != null)
093            {
094                byte[] userBytes = user.getBytes(StringUtils.UTF8);
095                System.arraycopy(userBytes, 0, buf, index, user.length());
096                index += user.length();
097            }
098            buf[index++] = 0;
099            out.write(buf, 0, index);
100
101    /*
102    The SOCKS server checks to see whether such a request should be granted
103    based on any combination of source IP address, destination IP address,
104    destination port number, the userid, and information it may obtain by
105    consulting IDENT, cf. RFC 1413.  If the request is granted, the SOCKS
106    server makes a connection to the specified port of the destination host.
107    A reply packet is sent to the client when this connection is established,
108    or when the request is rejected or the operation fails. 
109
110           +----+----+----+----+----+----+----+----+
111           | VN | CD | DSTPORT |      DSTIP        |
112           +----+----+----+----+----+----+----+----+
113    # of bytes:   1    1      2              4
114
115    VN is the version of the reply code and should be 0. CD is the result
116    code with one of the following values:
117
118    90: request granted
119    91: request rejected or failed
120    92: request rejected because SOCKS server cannot connect to
121    identd on the client
122    93: request rejected because the client program and identd
123    report different user-ids
124
125    The remaining fields are ignored.
126    */
127
128            int len = 6;
129            int s = 0;
130            while (s < len)
131            {
132                int i = in.read(buf, s, len - s);
133                if (i <= 0)
134                {
135                    throw new ProxyException(ProxyInfo.ProxyType.SOCKS4, 
136                        "stream is closed");
137                }
138                s += i;
139            }
140            if (buf[0] != 0)
141            {
142                throw new ProxyException(ProxyInfo.ProxyType.SOCKS4, 
143                    "server returns VN " + buf[0]);
144            }
145            if (buf[1] != 90)
146            {
147                try
148                {
149                    socket.close();
150                }
151                catch (Exception eee)
152                {
153                }
154                String message = "ProxySOCKS4: server returns CD " + buf[1];
155                throw new ProxyException(ProxyInfo.ProxyType.SOCKS4, message);
156            }
157            byte[] temp = new byte[2];
158            in.read(temp, 0, 2);
159        }
160        catch (RuntimeException e)
161        {
162            throw e;
163        }
164        catch (Exception e)
165        {
166            try
167            {
168               socket.close();
169            }
170            catch (Exception eee)
171            {
172            }
173            throw new ProxyException(ProxyInfo.ProxyType.SOCKS4, e.toString());
174        }
175    }
176
177}