All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jivesoftware.smack.proxy.Socks5ProxySocketFactory Maven / Gradle / Ivy

/**
 *
 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jivesoftware.smack.proxy;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.SocketFactory;

/**
 * Socket factory for Socks5 proxy
 * 
 * @author Atul Aggarwal
 */
public class Socks5ProxySocketFactory extends SocketFactory {
    private ProxyInfo proxy;

    public Socks5ProxySocketFactory(ProxyInfo proxy) {
        this.proxy = proxy;
    }

    public Socket createSocket(String host, int port) throws IOException,
            UnknownHostException {
        return socks5ProxifiedSocket(host, port);
    }

    public Socket createSocket(String host, int port, InetAddress localHost,
            int localPort) throws IOException, UnknownHostException {

        return socks5ProxifiedSocket(host, port);

    }

    public Socket createSocket(InetAddress host, int port) throws IOException {

        return socks5ProxifiedSocket(host.getHostAddress(), port);

    }

    public Socket createSocket(InetAddress address, int port,
            InetAddress localAddress, int localPort) throws IOException {

        return socks5ProxifiedSocket(address.getHostAddress(), port);

    }

    private Socket socks5ProxifiedSocket(String host, int port)
            throws IOException {
        Socket socket = null;
        InputStream in = null;
        OutputStream out = null;
        String proxy_host = proxy.getProxyAddress();
        int proxy_port = proxy.getProxyPort();
        String user = proxy.getProxyUsername();
        String passwd = proxy.getProxyPassword();

        try {
            socket = new Socket(proxy_host, proxy_port);
            in = socket.getInputStream();
            out = socket.getOutputStream();

            socket.setTcpNoDelay(true);

            byte[] buf = new byte[1024];
            int index = 0;

            /*
             * +----+----------+----------+ |VER | NMETHODS | METHODS |
             * +----+----------+----------+ | 1 | 1 | 1 to 255 |
             * +----+----------+----------+
             * 
             * The VER field is set to X'05' for this version of the protocol.
             * The NMETHODS field contains the number of method identifier
             * octets that appear in the METHODS field.
             * 
             * The values currently defined for METHOD are:
             * 
             * o X'00' NO AUTHENTICATION REQUIRED o X'01' GSSAPI o X'02'
             * USERNAME/PASSWORD o X'03' to X'7F' IANA ASSIGNED o X'80' to X'FE'
             * RESERVED FOR PRIVATE METHODS o X'FF' NO ACCEPTABLE METHODS
             */

            buf[index++] = 5;

            buf[index++] = 2;
            buf[index++] = 0; // NO AUTHENTICATION REQUIRED
            buf[index++] = 2; // USERNAME/PASSWORD

            out.write(buf, 0, index);

            /*
             * The server selects from one of the methods given in METHODS, and
             * sends a METHOD selection message:
             * 
             * +----+--------+ |VER | METHOD | +----+--------+ | 1 | 1 |
             * +----+--------+
             */
            // in.read(buf, 0, 2);
            fill(in, buf, 2);

            boolean check = false;
            switch ((buf[1]) & 0xff) {
            case 0: // NO AUTHENTICATION REQUIRED
                check = true;
                break;
            case 2: // USERNAME/PASSWORD
                if (user == null || passwd == null) {
                    break;
                }

                /*
                 * Once the SOCKS V5 server has started, and the client has
                 * selected the Username/Password Authentication protocol, the
                 * Username/Password subnegotiation begins. This begins with the
                 * client producing a Username/Password request:
                 * 
                 * +----+------+----------+------+----------+ |VER | ULEN |
                 * UNAME | PLEN | PASSWD |
                 * +----+------+----------+------+----------+ | 1 | 1 | 1 to 255
                 * | 1 | 1 to 255 | +----+------+----------+------+----------+
                 * 
                 * The VER field contains the current version of the
                 * subnegotiation, which is X'01'. The ULEN field contains the
                 * length of the UNAME field that follows. The UNAME field
                 * contains the username as known to the source operating
                 * system. The PLEN field contains the length of the PASSWD
                 * field that follows. The PASSWD field contains the password
                 * association with the given UNAME.
                 */
                index = 0;
                buf[index++] = 1;
                buf[index++] = (byte) (user.length());
                System.arraycopy(user.getBytes(), 0, buf, index, user.length());
                index += user.length();
                buf[index++] = (byte) (passwd.length());
                System.arraycopy(passwd.getBytes(), 0, buf, index,
                        passwd.length());
                index += passwd.length();

                out.write(buf, 0, index);

                /*
                 * The server verifies the supplied UNAME and PASSWD, and sends
                 * the following response:
                 * 
                 * +----+--------+ |VER | STATUS | +----+--------+ | 1 | 1 |
                 * +----+--------+
                 * 
                 * A STATUS field of X'00' indicates success. If the server
                 * returns a `failure' (STATUS value other than X'00') status,
                 * it MUST close the connection.
                 */
                // in.read(buf, 0, 2);
                fill(in, buf, 2);
                if (buf[1] == 0) {
                    check = true;
                }
                break;
            default:
            }

            if (!check) {
                try {
                    socket.close();
                } catch (Exception eee) {
                }
                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,
                        "fail in SOCKS5 proxy");
            }

            /*
             * The SOCKS request is formed as follows:
             * 
             * +----+-----+-------+------+----------+----------+ |VER | CMD |
             * RSV | ATYP | DST.ADDR | DST.PORT |
             * +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00'
             * | 1 | Variable | 2 |
             * +----+-----+-------+------+----------+----------+
             * 
             * Where:
             * 
             * o VER protocol version: X'05' o CMD o CONNECT X'01' o BIND X'02'
             * o UDP ASSOCIATE X'03' o RSV RESERVED o ATYP address type of
             * following address o IP V4 address: X'01' o DOMAINNAME: X'03' o IP
             * V6 address: X'04' o DST.ADDR desired destination address o
             * DST.PORT desired destination port in network octet order
             */

            index = 0;
            buf[index++] = 5;
            buf[index++] = 1; // CONNECT
            buf[index++] = 0;

            byte[] hostb = host.getBytes();
            int len = hostb.length;
            buf[index++] = 3; // DOMAINNAME
            buf[index++] = (byte) (len);
            System.arraycopy(hostb, 0, buf, index, len);
            index += len;
            buf[index++] = (byte) (port >>> 8);
            buf[index++] = (byte) (port & 0xff);

            out.write(buf, 0, index);

            /*
             * The SOCKS request information is sent by the client as soon as it
             * has established a connection to the SOCKS server, and completed
             * the authentication negotiations. The server evaluates the
             * request, and returns a reply formed as follows:
             * 
             * +----+-----+-------+------+----------+----------+ |VER | REP |
             * RSV | ATYP | BND.ADDR | BND.PORT |
             * +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00'
             * | 1 | Variable | 2 |
             * +----+-----+-------+------+----------+----------+
             * 
             * Where:
             * 
             * o VER protocol version: X'05' o REP Reply field: o X'00'
             * succeeded o X'01' general SOCKS server failure o X'02' connection
             * not allowed by ruleset o X'03' Network unreachable o X'04' Host
             * unreachable o X'05' Connection refused o X'06' TTL expired o
             * X'07' Command not supported o X'08' Address type not supported o
             * X'09' to X'FF' unassigned o RSV RESERVED o ATYP address type of
             * following address o IP V4 address: X'01' o DOMAINNAME: X'03' o IP
             * V6 address: X'04' o BND.ADDR server bound address o BND.PORT
             * server bound port in network octet order
             */

            // in.read(buf, 0, 4);
            fill(in, buf, 4);

            if (buf[1] != 0) {
                try {
                    socket.close();
                } catch (Exception eee) {
                }
                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,
                        "server returns " + buf[1]);
            }

            switch (buf[3] & 0xff) {
            case 1:
                // in.read(buf, 0, 6);
                fill(in, buf, 6);
                break;
            case 3:
                // in.read(buf, 0, 1);
                fill(in, buf, 1);
                // in.read(buf, 0, buf[0]+2);
                fill(in, buf, (buf[0] & 0xff) + 2);
                break;
            case 4:
                // in.read(buf, 0, 18);
                fill(in, buf, 18);
                break;
            default:
            }
            return socket;

        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (Exception eee) {
            }
            String message = "ProxySOCKS5: " + e.toString();
            if (e instanceof Throwable) {
                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, message,
                        (Throwable) e);
            }
            throw new IOException(message);
        }
    }

    private void fill(InputStream in, byte[] buf, int len) throws IOException {
        int s = 0;
        while (s < len) {
            int i = in.read(buf, s, len - s);
            if (i <= 0) {
                throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "stream "
                        + "is closed");
            }
            s += i;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy