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.ByteArrayOutputStream;
020import java.io.DataInputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.net.InetAddress;
025import java.net.InetSocketAddress;
026import java.net.Socket;
027import java.nio.charset.StandardCharsets;
028
029import org.jivesoftware.smack.util.OutputStreamUtil;
030
031/**
032 * Socket factory for socks4 proxy.
033 *
034 * @author Atul Aggarwal
035 */
036public class Socks4ProxySocketConnection implements ProxySocketConnection {
037    private final ProxyInfo proxy;
038
039    Socks4ProxySocketConnection(ProxyInfo proxy) {
040        this.proxy = proxy;
041    }
042
043    @Override
044    public void connect(Socket socket, String host, int port, int timeout)
045                    throws IOException {
046        String proxy_host = proxy.getProxyAddress();
047        int proxy_port = proxy.getProxyPort();
048        String user = proxy.getProxyUsername();
049
050        socket.connect(new InetSocketAddress(proxy_host, proxy_port), timeout);
051        InputStream in = socket.getInputStream();
052        DataInputStream dis = new DataInputStream(in);
053        OutputStream out = socket.getOutputStream();
054
055        ByteArrayOutputStream outBuf = new ByteArrayOutputStream();
056        byte[] inBuf;
057
058    /*
059    1) CONNECT
060
061    The client connects to the SOCKS server and sends a CONNECT request when
062    it wants to establish a connection to an application server. The client
063    includes in the request packet the IP address and the port number of the
064    destination host, and userid, in the following format.
065
066           +----+----+----+----+----+----+----+----+----+----+....+----+
067           | VN | CD | DSTPORT |      DSTIP        | USERID       |NULL|
068           +----+----+----+----+----+----+----+----+----+----+....+----+
069    # of bytes:   1    1      2              4           variable       1
070
071    VN is the SOCKS protocol version number and should be 4. CD is the
072    SOCKS command code and should be 1 for CONNECT request. NULL is a byte
073    of all zero bits.
074    */
075
076        outBuf.write(4);
077        outBuf.write(1);
078
079        outBuf.write(port >>> 8);
080        outBuf.write(port & 0xff);
081
082        InetAddress inetAddress = InetAddress.getByName(proxy_host);
083        byte[] byteAddress = inetAddress.getAddress();
084        outBuf.write(byteAddress);
085
086        if (user != null) {
087            byte[] userBytes = user.getBytes(StandardCharsets.UTF_8);
088            outBuf.write(userBytes);
089        }
090        outBuf.write(0);
091        OutputStreamUtil.writeResetAndFlush(outBuf, out);
092
093    /*
094    The SOCKS server checks to see whether such a request should be granted
095    based on any combination of source IP address, destination IP address,
096    destination port number, the userid, and information it may obtain by
097    consulting IDENT, cf. RFC 1413.  If the request is granted, the SOCKS
098    server makes a connection to the specified port of the destination host.
099    A reply packet is sent to the client when this connection is established,
100    or when the request is rejected or the operation fails.
101
102           +----+----+----+----+----+----+----+----+
103           | VN | CD | DSTPORT |      DSTIP        |
104           +----+----+----+----+----+----+----+----+
105    # of bytes:   1    1      2              4
106
107    VN is the version of the reply code and should be 0. CD is the result
108    code with one of the following values:
109
110    90: request granted
111    91: request rejected or failed
112    92: request rejected because SOCKS server cannot connect to
113    identd on the client
114    93: request rejected because the client program and identd
115    report different user-ids
116
117    The remaining fields are ignored.
118    */
119
120        inBuf = new byte[6];
121        dis.readFully(inBuf);
122        if (inBuf[0] != 0) {
123            throw new ProxyException(ProxyInfo.ProxyType.SOCKS4,
124                "server returns VN " + inBuf[0]);
125        }
126        if (inBuf[1] != 90) {
127            String message = "ProxySOCKS4: server returns CD " + inBuf[1];
128            throw new ProxyException(ProxyInfo.ProxyType.SOCKS4, message);
129        }
130        inBuf = new byte[2];
131        dis.readFully(inBuf);
132    }
133}