/*
* This file is part of the OWASP Proxy, a free intercepting proxy library.
* Copyright (C) 2008-2010 Rogan Dawes <rogan@dawes.za.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to:
* The Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package org.owasp.proxy.socks.impl;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.owasp.proxy.util.AsciiString;
/**
* 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);
di.readUnsignedByte(); // reserved
addrType = di.readUnsignedByte();
byte addr[];
switch (addrType) {
case SOCKS_ATYP_IPV4:
addr = new byte[4];
di.readFully(addr);
host = InetAddress.getByAddress(addr).getHostAddress();
break;
case SOCKS_ATYP_IPV6:
addr = new byte[SOCKS_IPV6_LENGTH];
di.readFully(addr);
host = InetAddress.getByAddress(addr).getHostAddress();
break;
case SOCKS_ATYP_DOMAINNAME:
addr = new byte[di.readUnsignedByte()];// Next byte shows the length
di.readFully(addr);
host = AsciiString.create(addr);
break;
default:
throw (new SocksException(SocksConstants.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(
SocksConstants.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;
}
// 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;
}