view src/net/sourceforge/jsocks/Socks5Message.java @ 98:16e023784917

protect both read calls with test for blocking
author Carl Byington <carl@five-ten-sg.com>
date Tue, 17 Jun 2014 11:35:01 -0700
parents 0ce5cc452d02
children 205ee2873330
line wrap: on
line source

package net.sourceforge.jsocks;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 SOCKS5 request/response message.
*/

public class Socks5Message extends ProxyMessage {
    /** Address type of given message*/
    public int addrType;

    byte[] data;

    /**
     Server error response.
     @param cmd Error code.
    */
    public Socks5Message(int cmd) {
        super(cmd, null, 0);
        data = new byte[3];
        data[0] = SOCKS_VERSION; //Version.
        data[1] = (byte)cmd;     //Reply code for some kind of failure.
        data[2] = 0;             //Reserved byte.
    }

    /**
     Construct client request or server response.
     @param cmd - Request/Response code.
     @param ip  - IP field.
     @paarm port - port field.
    */
    public Socks5Message(int cmd, InetAddress ip, int port) {
        super(cmd, ip, port);
        this.host = ip == null ? "0.0.0.0" : ip.getHostName();
        this.version = SOCKS_VERSION;
        byte[] addr;

        if (ip == null) {
            addr = new byte[4];
            addr[0] = addr[1] = addr[2] = addr[3] = 0;
        }
        else
            addr = ip.getAddress();

        addrType = addr.length == 4 ? SOCKS_ATYP_IPV4
                   : SOCKS_ATYP_IPV6;
        data = new byte[6 + addr.length];
        data[0] = (byte) SOCKS_VERSION;       //Version
        data[1] = (byte) command;         //Command
        data[2] = (byte) 0;           //Reserved byte
        data[3] = (byte) addrType;        //Address type
        //Put Address
        System.arraycopy(addr, 0, data, 4, addr.length);
        //Put port
        data[data.length - 2] = (byte)(port >> 8);
        data[data.length - 1] = (byte)(port);
    }


    /**
     Construct client request or server response.
     @param cmd - Request/Response code.
     @param hostName  - IP field as hostName, uses ADDR_TYPE of HOSTNAME.
     @paarm port - port field.
    */
    public Socks5Message(int cmd, String hostName, int port) {
        super(cmd, null, port);
        this.host = hostName;
        this.version = SOCKS_VERSION;
        //System.out.println("Doing ATYP_DOMAINNAME");
        addrType = SOCKS_ATYP_DOMAINNAME;
        byte addr[] = hostName.getBytes();
        data = new byte[7 + addr.length];
        data[0] = (byte) SOCKS_VERSION;       //Version
        data[1] = (byte) command;         //Command
        data[2] = (byte) 0;           //Reserved byte
        data[3] = (byte) SOCKS_ATYP_DOMAINNAME;   //Address type
        data[4] = (byte) addr.length;     //Length of the address
        //Put Address
        System.arraycopy(addr, 0, data, 5, addr.length);
        //Put port
        data[data.length - 2] = (byte)(port >> 8);
        data[data.length - 1] = (byte)(port);
    }

    /**
      Initialises Message from the stream. Reads server response from
      given stream.
      @param in Input stream to read response from.
      @throws SocksException If server response code is not SOCKS_SUCCESS(0), or
      if any error with protocol occurs.
      @throws IOException If any error happens with I/O.
    */
    public Socks5Message(InputStream in) throws SocksException,
        IOException {
        this(in, true);
    }

    /**
      Initialises Message from the stream. Reads server response or client
      request from given stream.

      @param in Input stream to read response from.
      @param clinetMode If true read server response, else read client request.
      @throws SocksException If server response code is not SOCKS_SUCCESS(0) and
      reading in client mode, or if any error with protocol occurs.
      @throws IOException If any error happens with I/O.
    */
    public Socks5Message(InputStream in, boolean clientMode)throws SocksException,
        IOException {
        read(in, clientMode);
    }


    /**
      Initialises Message from the stream. Reads server response from
      given stream.
      @param in Input stream to read response from.
      @throws SocksException If server response code is not SOCKS_SUCCESS(0), or
      if any error with protocol occurs.
      @throws IOException If any error happens with I/O.
    */
    public void read(InputStream in) throws SocksException,
        IOException {
        read(in, true);
    }


    /**
      Initialises Message from the stream. Reads server response or client
      request from given stream.

      @param in Input stream to read response from.
      @param clinetMode If true read server response, else read client request.
      @throws SocksException If server response code is not SOCKS_SUCCESS(0) and
      reading in client mode, or if any error with protocol occurs.
      @throws IOException If any error happens with I/O.
    */
    public void read(InputStream in, boolean clientMode) throws SocksException,
        IOException {
        data = null;
        ip = null;
        DataInputStream di = new DataInputStream(in);
        version = di.readUnsignedByte();
        command = di.readUnsignedByte();

        if (clientMode && command != 0)
            throw new SocksException(command);

        @SuppressWarnings("unused")
        int reserved = di.readUnsignedByte();
        addrType = di.readUnsignedByte();
        byte addr[];

        switch (addrType) {
            case SOCKS_ATYP_IPV4:
                addr = new byte[4];
                di.readFully(addr);
                host = bytes2IPV4(addr, 0);
                break;

            case SOCKS_ATYP_IPV6:
                addr = new byte[SOCKS_IPV6_LENGTH];//I believe it is 16 bytes,huge!
                di.readFully(addr);
                host = bytes2IPV6(addr, 0);
                break;

            case SOCKS_ATYP_DOMAINNAME:
                //System.out.println("Reading ATYP_DOMAINNAME");
                addr = new byte[di.readUnsignedByte()];//Next byte shows the length
                di.readFully(addr);
                host = new String(addr);
                break;

            default:
                throw(new SocksException(Proxy.SOCKS_JUST_ERROR));
        }

        port = di.readUnsignedShort();

        if (addrType != SOCKS_ATYP_DOMAINNAME && doResolveIP) {
            try {
                ip = InetAddress.getByName(host);
            }
            catch (UnknownHostException uh_ex) {
            }
        }
    }

    /**
     Writes the message to the stream.
     @param out Output stream to which message should be written.
    */
    public void write(OutputStream out)throws SocksException,
        IOException {
        if (data == null) {
            Socks5Message msg;

            if (addrType == SOCKS_ATYP_DOMAINNAME)
                msg = new Socks5Message(command, host, port);
            else {
                if (ip == null) {
                    try {
                        ip = InetAddress.getByName(host);
                    }
                    catch (UnknownHostException uh_ex) {
                        throw new SocksException(Proxy.SOCKS_JUST_ERROR);
                    }
                }

                msg = new Socks5Message(command, ip, port);
            }

            data = msg.data;
        }

        out.write(data);
    }

    /**
     Returns IP field of the message as IP, if the message was created
     with ATYP of HOSTNAME, it will attempt to resolve the hostname,
     which might fail.
     @throws UnknownHostException if host can't be resolved.
    */
    public InetAddress getInetAddress() throws UnknownHostException {
        if (ip != null) return ip;

        return (ip = InetAddress.getByName(host));
    }

    /**
      Returns string representation of the message.
    */
    public String toString() {
        String s =
            "Socks5Message:" + "\n" +
            "VN   " + version + "\n" +
            "CMD  " + command + "\n" +
            "ATYP " + addrType + "\n" +
            "ADDR " + host + "\n" +
            "PORT " + port + "\n";
        return s;
    }


    /**
     *Wether to resolve hostIP returned from SOCKS server
     *that is wether to create InetAddress object from the
     *hostName string
     */
    static public boolean resolveIP() { return doResolveIP;}

    /**
     *Wether to resolve hostIP returned from SOCKS server
     *that is wether to create InetAddress object from the
     *hostName string
     *@param doResolve Wether to resolve hostIP from SOCKS server.
     *@return Previous value.
     */
    static public boolean resolveIP(boolean doResolve) {
        boolean old = doResolveIP;
        doResolveIP = doResolve;
        return old;
    }

    /*
    private static final void debug(String s){
       if(DEBUG)
          System.out.print(s);
    }
    private static final boolean DEBUG = false;
    */

    //SOCKS5 constants
    public static final int SOCKS_VERSION        = 5;

    public static final int SOCKS_ATYP_IPV4      = 0x1; //Where is 2??
    public static final int SOCKS_ATYP_DOMAINNAME    = 0x3; //!!!!rfc1928
    public static final int SOCKS_ATYP_IPV6      = 0x4;

    public static final int SOCKS_IPV6_LENGTH        = 16;

    static boolean doResolveIP = true;

}