/*******************************************************************************
* gMix open source project - https://svs.informatik.uni-hamburg.de/gmix/
* Copyright (C) 2014 SVS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package userGeneratedContent.testbedPlugIns.layerPlugIns.layer5application.socks_v0_001.socks;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import staticContent.framework.util.Util;
import userGeneratedContent.testbedPlugIns.layerPlugIns.layer5application.socks_v0_001.Config;
/**
* Handles SOCKS4 and SOCKS5 protocol.
*
* @author Haseloff, Schmitz, Sprotte
*
*/
public class SocksHandler extends Thread {
private InputStream fromClient;
private OutputStream toClient;
private Forwarder toUserForwarder;
private Forwarder toWANforwarder;
private DatagramSocket fromToWebserver;
private int udpWebserverPort;
// private ForwarderSocks5UDPtoWebserver udpForwarderToWebserver;
// private ForwarderSocks5UDPtoClient udpForwarderToClient;
Socket connectS = null;
ServerSocket ss = null;
Socket bindS = null;
Socket udpS = null;
Config config;
/**
* SOCKS handler for a single socks connection (one handler per connection
* required).
*
* @param fromClient
* InputStream from client (Mix)
* @param toClient
* OutputStream to client (Mix)
*/
public SocksHandler(InputStream fromClient, OutputStream toClient,
Config config) {
this.fromClient = fromClient;
this.toClient = toClient;
this.config = config;
start();
}
/**
* Terminates the toUser- and toWANforwarder
*/
public void close() {
if (toUserForwarder != null)
toUserForwarder.close();
else
try {
fromClient.close();
} catch (IOException e) {
if (config.DEBUG)
e.printStackTrace();
}
if (toWANforwarder != null)
toWANforwarder.close();
else
try {
toClient.close();
} catch (IOException e) {
if (config.DEBUG)
e.printStackTrace();
}
}
/**
* Reads a couple of bytes depending on the address type and returns this
* address as an InetAddress-Object.
*
* @param aTyp
* valid values: 0x01, 0x03, 0x04
* @return InetAddress
*/
private InetAddress getInetAddress(byte aTyp) {
byte[] byteAddress = null;
int length;
InetAddress address = null;
try {
switch (aTyp) {
case 0x01: // IP v4
length = 4;
byteAddress = Util.forceRead(fromClient, length);
address = InetAddress.getByAddress(byteAddress);
break;
case 0x03: // Domain, first byte defines the length of it
length = Util.unsignedByteToShort((byte) fromClient.read());
byteAddress = Util.forceRead(fromClient, length);
String stringAddress = Util
.getStringWithoutNewLines(byteAddress);
address = InetAddress.getByName(stringAddress);
break;
case 0x04: // IP v6
length = 16;
byteAddress = Util.forceRead(fromClient, length);
address = InetAddress.getByAddress(byteAddress);
break;
default:
this.sendSocks5FailReply((byte) 0x05, (byte) 0x08, (byte) 0x00); // reply
// 0x08:
// Address
// type
// not
// supported
System.out
.println("SocksHandler: i do only support IP V4, Domains or IP V6 addresses");
if (config.TALK_A_LOT == true) {
System.out
.println("SocksHandler: Sent CONNECT reply: 0x08");
}
close();
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return address;
}
/**
* Sends a SOCKS5 Method Reply Message to user.
*
* +----+--------+ |VER | METHOD | +----+--------+ | 1 | 1 | +----+--------+
*
* @param toClient
* OutputStream of Client
* @param method
* Chosen authentifiaction method
*/
private void sendSocks5MethodReply(byte method) {
try {
toClient.write(0x05); // version
toClient.write(method); // method
toClient.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*
* Sends SOCKS5 reply for the connect (0x01) command. The message looks
* like:
*
* +----+-----+-------+------+----------+----------+ |VER | REP | RSV | ATYP
* | BND.ADDR | BND.PORT | +----+-----+-------+------+----------+----------+
* | 1 | 1 | X'00' | 1 | Variable | 2 |
* +----+-----+-------+------+----------+----------+
*
* @param version
* SOCKS Version
* @param rep
* SOCKS5 reply field
* @param atyp
* valid values: 0x01, 0x03, 0x04
* @param socket
* Socket which the message is send to
*/
private void sendSocks5ConnectReply(byte version, byte rep, byte atyp,
Socket socket) { // with Socket
try {
toClient.write(version); // version
toClient.write(rep); // rep (succeeded) TODO: send real result...
toClient.write(0x00); // reserved
toClient.write(atyp); // atyp
toClient.write(socket.getInetAddress().getAddress()); // ip address
toClient.write(Util.shortToByteArray(socket.getPort())); // port
toClient.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*
* Sends SOCKS5 reply for 2nd connection for the BIND command (0x02).
*
* @param version
* SOCKS Version
* @param rep
* SOCKS5 reply field
* @param atyp
* valid values: 0x01, 0x03, 0x04
* @param serversocket
*/
private void sendSocks5BindReply(byte version, byte rep, byte atyp,
ServerSocket serversocket) { // with ServerSocket (for bind failed)
try {
toClient.write(version); // version
toClient.write(rep); // rep (succeeded) TODO: send real result...
toClient.write(0x00); // reserved
toClient.write(atyp); // atyp
toClient.write(InetAddress.getLocalHost().getAddress()); // ip
// address
toClient.write(Util.shortToByteArray(serversocket.getLocalPort())); // port
toClient.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*
* Sends SOCKS5 reply for UDP Associate command (0x03)
*
* @param version
* SOCKS Version
* @param rep
* SOCKS5 reply field
* @param atyp
* valid values: 0x01, 0x03, 0x04
*/
private void sendSocks5UDPReply(byte version, byte rep, byte atyp,
DatagramSocket fromToWebserver) {
try {
toClient.write(version); // version
toClient.write(rep); // rep (succeeded) TODO: send real result...
toClient.write(0x00); // reserved
toClient.write(atyp); // atyp
byte[] dstaddress = InetAddress.getLocalHost().getAddress();
toClient.write(dstaddress); // ip address of ServerSockt local proxy
byte[] dstport = Util.shortToByteArray(fromToWebserver
.getLocalPort());
toClient.write(dstport); // port of ServerSockt local proxy
toClient.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* sendSocks5FailReply sends the same error message (0x04) for:
*
* X'03' Network unreachable X'04' Host unreachable X'05' Connection refused
* X'06' TTL expired
*
* and never sends the following messages: X'01' general SOCKS server
* failure X'02' connection not allowed by ruleset
*
* @param version
* SOCKS Version
* @param rep
* SOCKS5 reply field
* @param atyp
* valid values: 0x01, 0x03, 0x04
*
*
*/
private void sendSocks5FailReply(byte version, byte rep, byte atyp) { // with
// Socket
try {
toClient.write(version); // version
toClient.write(rep); // rep
toClient.write(0x00); // reserved
toClient.write(atyp); // atyp
InetAddress inetAddress = null;
try {
inetAddress = InetAddress.getByName("0.0.0.0");
} catch (UnknownHostException e) {
e.printStackTrace();
}
byte[] dstaddress = inetAddress.getAddress();
toClient.write(dstaddress); // ip address
toClient.write(Util.shortToByteArray(00000)); // port
toClient.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*
* Sends SOCKS4 reply for connect command (0x01)
*
* +----+----+----+----+----+----+----+----+ | VN | CD | DSTPORT | DSTIP |
* +----+----+----+----+----+----+----+----+ # of bytes: 1 1 2 4
*
* @param socket
* Socket which is connected to webserver
* @param rep
* suiting SOCKS4 reply
*/
private void sendSocks4ConnectReply(Socket socket, int rep) {
try {
byte[] address = socket.getInetAddress().getAddress();
byte[] port = Util.shortToByteArray(socket.getPort());
// System.out.println("Length of address:port byte[]: "+address.length+":"+port.length);
// System.out.println(Util.unsignedShortToInt(port));
toClient.write((byte) 0x00);
// System.out.println(Util.byteArrayToInt(rep));
toClient.write((byte) rep); // reply code
toClient.write(port);
toClient.write(address);
toClient.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Sends SOCKS4 reply for BIND command (0x02). Message has the same
* structure as the reply for CONNECT command.
*
* @param socket
* ServerSocket which the SocksHandler provides for connections
* from webserver
* @param rep
* suiting SOCKS4 reply
*/
private void sendSocks4BindReply(ServerSocket socket, int rep) {
try {
InetAddress address = InetAddress.getLocalHost();
byte[] port = Util.shortToByteArray(socket.getLocalPort());
// System.out.println("Length of address:port byte[]: "+address.length+":"+port.length);
// System.out.println(Util.unsignedShortToInt(port));
toClient.write((byte) 0x00);
toClient.write((byte) rep); // reply code
toClient.write(port);
toClient.write(address.getAddress());
toClient.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Creates a toUser- and a toWANForwarder and just forwards data from Input-
* to Outputstream in two directions.
*
* @param from
* Client InputStream from which the data should be read
* @param to
* Client OutputStream to which the data should be written
* @param socket
* Socket conneceted to webserver
*/
private void forwardData(InputStream from, OutputStream to, Socket socket) {
try {
toUserForwarder = new Forwarder(from, socket.getOutputStream(),
config); // from Client to Webserver
toWANforwarder = new Forwarder(socket.getInputStream(), to, config); // from
// Webserver
// to
// Client
toUserForwarder.start();
toWANforwarder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Reads SOCKS5 Request from user and interprets SOCKS5 command. Calls
* specific SOCKS5 command handle-Method.
*
* @param version
* SOCKS version
*/
private void handleSocks5(byte version) {
try {
if (config.SKIP_ROUNDTRIP == false) {
int nMethods = Util.unsignedByteToShort((byte) fromClient
.read());
byte[] methods = Util.forceRead(fromClient, nMethods);
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: " + nMethods
+ " method(s) (in hex): " + Util.toHex(methods));
}
// TODO: parse methods (necessary for authentication only; not
// yet required)
/**
* Proxy -2-> Client +----+--------+ |VER | METHOD |
* +----+--------+ | 1 | 1 | +----+--------+
*/
// send "NO AUTHENTICATION REQUIRED" message
byte method = 0x00;
this.sendSocks5MethodReply((byte) method);
}
/**
* Client -4-> Proxy -5-> Server
* +----+-----+-------+------+----------+----------+ |VER | CMD |
* RSV | ATYP | DST.ADDR | DST.PORT |
* +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00'
* | 1 | Variable | 2 |
* +----+-----+-------+------+----------+----------+
*/
// read socks request:
if (config.SKIP_ROUNDTRIP == false) {
fromClient.read(); // read version
}
byte command = (byte) fromClient.read(); // read command
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: received command: 0x0"+ command);
}
/* int reserved = */fromClient.read(); // skip reserved
byte aTyp = (byte) fromClient.read(); // read aTyp
// define address typ
InetAddress address = null;
address = getInetAddress(aTyp);
byte[] bPort = Util.forceRead(fromClient, 2); // read DST.PORT
int port = Util.unsignedShortToInt(bPort);
switch (command) {
case 0x01: // CONNECT
if (config.TALK_A_LOT == true)
System.out.println("SocksHandler: Got CONNECT Request from Client.");
if (config.TALK_A_LOT == true)
System.out.println("SocksHandler: destination Webserver: " + address + ":" + port);
this.handleSocks5Connect(address, port);
break;
case 0x02: // BIND
if (config.TALK_A_LOT == true)
System.out.println("SocksHandler: Got BIND Request from Client.");
if (config.TALK_A_LOT == true)
System.out.println("SocksHandler: destination Webserver: " + address + ":" + port);
this.handleSocks5Bind();
break;
case 0x03: // UDP Associate
if (config.TALK_A_LOT == true)
System.out.println("SocksHandler: Got UDP Associate Request from Client.");
if (config.TALK_A_LOT == true)
System.out.println("SocksHandler: source client: " + address + ":" + port);
this.handleSocks5UDP(address, port);
break;
default: // not a valid Socks5 command
sendSocks5FailReply((byte) 0x05, (byte) 0x07, (byte) 0x01); // reply
// 0x07:
// Command
// not
// supported
System.out.println("SocksHandler: i do only support CONNECT, BIND or UDP command");
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: Sent CONNECT reply: 0x07");
}
close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Handles SOCKS5 connect request from user. Connects to webserver, reply to
* user and forwards the data.
*
* @param address
* address of webserver to which a connection should be
* established
* @param port
* port of webserver to which a connection should be esablished
*/
private void handleSocks5Connect(InetAddress address, int port) {
// try to connect Proxy --> Server
try {
connectS = new Socket(address, port);
/**
* send reply (RFC 1928, p. 5) Proxy --> Client, after connection
* Proxy <--> Server established
* +----+-----+-------+------+----------+----------+ |VER | REP |
* RSV | ATYP | BND.ADDR | BND.PORT |
* +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00'
* | 1 | Variable | 2 |
* +----+-----+-------+------+----------+----------+
*/
this.sendSocks5ConnectReply((byte) 0x05, (byte) 0x00, (byte) 0x01,
connectS);
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: Sent CONNECT reply: 0x01");
}
// from now on we will just forward data:
this.forwardData(fromClient, toClient, connectS);
} catch (IOException e) {
if (e instanceof ConnectException) {
sendSocks5FailReply((byte) 0x05, (byte) 0x04, (byte) 0x01); // reply
// 0x04:
// Host
// unreachable
System.out.println("SocksHandler: Host unreachable");
}
if (config.DEBUG == true) {
e.printStackTrace();
}
close();
}
}
/**
* Handles SOCKS5 BIND request from user. Binds ServerSocket to "random"
* port and sends first reply to user (includes the port on which the socket
* is bound). Waiting for connection form webserver; if established sends
* second reply to user.
*/
private void handleSocks5Bind() {
int addRandomPort = Util.getRandomInt(10, 10000);
int ssPort = config.MIX_BIND_PORT + addRandomPort; // flexible for more
// binds
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: Starting new SOCKS 5 BIND ServerSocket");
}
try {
try {
ss = new ServerSocket(ssPort);
} catch (IOException e) {
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: can't bind ServerSocket because port is already in use... I'm trying another one");
}
this.handleSocks5Bind();
}
System.out.println("SocksHandler: ServerSocket is bound to: "
+ InetAddress.getLocalHost() + ":" + ssPort);
// send first reply after ServerSocket is bound
this.sendSocks5BindReply((byte) 0x05, (byte) 0x00, (byte) 0x01, ss);
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: Sent ServerSocket is bound reply");
}
bindS = ss.accept(); // waits for connection from Webserver
// send second reply
if (bindS == null) // Connection between Sever and Proxy failed //
// TODO: real reply
{
System.out.println("SocksHandler: Connection from Webserver to Proxy failed");
this.sendSocks5BindReply((byte) 0x05, (byte) 0x05, (byte) 0x01,
ss); // rep: connection refused
close();
return;
} else
// Connection between Sever and Proxy established
{
System.out.println("SocksHandler: Connection from Webserver to Proxy established");
this.sendSocks5ConnectReply((byte) 0x05, (byte) 0x00,
(byte) 0x01, bindS);
this.forwardData(fromClient, toClient, bindS); // secondary
// connection
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Handles SOCKS5 UDP request from user. Binds DatagramSocket to "random"
* port and sends reply to user (includes the port to which the
* DatagramSocket is bound). Initializes an UDP forwarder to client and an
* UDP forwarder to webserver.
*
* @param address
* address of client
* @param port
* port of client
*/
private void handleSocks5UDP(InetAddress address, int port) {
// "buf" for client ip and port
InetAddress clientAddress = address;
int clientPort = port;
int addRandomPort = Util.getRandomInt(10, 10000);
udpWebserverPort = config.MIX_DATAGRAM_PORT + addRandomPort; // flexible
// for
// more
// DatagramSockets
try {
fromToWebserver = new DatagramSocket(udpWebserverPort);
} catch (IOException e) {
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: can't bind DatagramSocket because port is already in use... I'm trying another one");
}
this.handleSocks5UDP(address, port);
}
System.out.println("SocksHandler: Bound DatagramSocket to Webserver to " + udpWebserverPort);
this.sendSocks5UDPReply((byte) 0x05, (byte) 0x00, (byte) 0x01,
fromToWebserver); // *** Step 6 ***
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: Sent Socks5UDPReply to Client");
}
/* udpForwarderToWebserver = */
new ForwarderSocks5UDPtoWebserver(fromClient, fromToWebserver, config);
/* udpForwarderToClient = */
new ForwarderSocks5UDPtoClient(toClient, clientAddress, clientPort,
fromToWebserver, config);
}
/**
* Reads SOCKS4 Request from user and interprets SOCKS5 command. Calls
* specific SOCKS4 command handle-Method.
*
* @param version
* SOCKS version
*/
private void handleSocks4(byte version) {
/**
* Client --> Proxy 1) CONNECT
*
* +----+----+----+----+----+----+----+----+----+----+....+----+ | VN |
* CD | DSTPORT | DSTIP | USERID |NULL|
* +----+----+----+----+----+----+----+----+----+----+....+----+ # of
* bytes: 1 1 2 4 variable 1
*/
try {
byte command = (byte) fromClient.read();
byte[] bPort = Util.forceRead(fromClient, 2); // read DST.PORT
int port = Util.unsignedShortToInt(bPort);
InetAddress address = null;
address = getInetAddress((byte) 0x01); // read IPv4 Address
// we skip the request check, so we don't need USERID and NULL
// TODO: request check (SRCIP, DSTIP, DSTPORT, USERID)
/* int userid = */fromClient.read();
if (config.TALK_A_LOT == true)
System.out.println("SocksHandler: destination Webserver: " + address + ":" + port);
if (command == 0x01) // CONNECT command
{
if (config.TALK_A_LOT == true)
System.out.println("SocksHandler: Got CONNECT command from Client");
this.handleSocks4Connect(address, port);
} else if (command == 0x02) // BIND command
{
if (config.TALK_A_LOT == true)
System.out.println("SocksHandler: Got BIND command from Client");
this.handleSocks4Bind();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Handles SOCKS4 connect request from user. Connects to webserver, reply to
* user and forwards the data.
*
* @param address
* address of webserver to which a connection should be
* established
* @param port
* port of webserver to which a connection should be esablished
*/
private void handleSocks4Connect(InetAddress address, int port) {
Socket connectS4 = null; // Socket for connection between Proxy and
// Webserver
try {
connectS4 = new Socket(address, port);
// System.out.println("SocksHandler: connectS4 Status: "+
// connectS.isConnected());
System.out.println("SocksHandler: connection between Proxy and Webserver established");
this.sendSocks4ConnectReply(connectS4, 90); // 90: request granted
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: sent CONNECT reply to Client");
}
Thread.sleep(1000);
this.forwardData(fromClient, toClient, connectS4);
System.out.println("SocksHandler: data forwarding started");
} catch (IOException e) {
this.sendSocks4ConnectReply(connectS4, 91); // 91: request rejected
// or failed
e.printStackTrace();
close(); // closing Client connection
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Handles SOCKS4 BIND request from user. Binds ServerSocket to "random"
* port and sends first reply to user (includes the port on which the socket
* is bound). Waiting for connection form webserver; if established sends
* second reply to user.
*/
private void handleSocks4Bind() {
int addRandomPort = Util.getRandomInt(10, 10000);
int ssPort = config.MIX_BIND_PORT + addRandomPort; // flexible for more
// binds
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: Starting new SOCKS 4 BIND ServerSocket");
}
try {
try {
ss = new ServerSocket(ssPort);
} catch (IOException e) {
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: can't bind ServerSocket because port is already in use... I'm trying another one");
}
this.handleSocks4Bind();
}
System.out.println("SocksHandler: ServerSocket is bound to: " + InetAddress.getLocalHost() + ":" + ssPort);
this.sendSocks4BindReply(ss, 90);
bindS = ss.accept(); // waiting for connection from Webserver
// TODO: CHECK originating host
if (bindS == null) // Connection between Sever and Proxy failed
{
System.out.println("SocksHandler: Connection from Sever to Proxy failed");
this.sendSocks4BindReply(ss, 91); // rep: connection refused
close();
return;
} else
// Connection between Sever and Proxy established
{
System.out.println("SocksHandler: Connection from Sever to Proxy established");
this.sendSocks4BindReply(ss, 90);
this.forwardData(fromClient, toClient, bindS); // secondary
// connection
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Reads the first byte of a message from user, to decide which SOCKS
* version should be use. Calls the specific handle-Method.
*/
@Override
public void run() {
// read SOCKS "version identifier/method selection message" (RFC 1928,
// p. 3)
/**
* Client -1-> Proxy +----+----------+----------+ |VER | NMETHODS |
* METHODS | +----+----------+----------+ | 1 | 1 | 1 to 255 |
* +----+----------+----------+
*/
byte version = 0x00;
try {
version = (byte) fromClient.read();
} catch (IOException e) {
e.printStackTrace();
}
if (config.TALK_A_LOT == true) {
System.out.println("SocksHandler: reading Identifier Message from Client.");
}
if (version == 0x05) {
if (config.TALK_A_LOT == true)
System.out.println("SocksHandler: its a socks 5 connection");
this.handleSocks5(version);
} else if (version == 0x04) {
if (config.TALK_A_LOT == true)
System.out.println("SocksHandler: its a socks 4 connection");
this.handleSocks4(version);
} else {
if (config.TALK_A_LOT == true)
System.out.println("SocksHandler: connections does not seem to be socks");
close();
return;
}
}
}