add socks 4/4a support
This commit is contained in:
@ -0,0 +1,283 @@
|
||||
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
|
||||
* with an additional exception. For further details, see the
|
||||
* licensing terms in I2PTunnel.java.
|
||||
*
|
||||
* Copyright (c) 2004 by human
|
||||
*/
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.util.HexDump;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/*
|
||||
* Class that manages SOCKS 4/4a connections, and forwards them to
|
||||
* destination hosts or (eventually) some outproxy.
|
||||
*
|
||||
* @author zzz modded from SOCKS5Server
|
||||
*/
|
||||
public class SOCKS4aServer extends SOCKSServer {
|
||||
private static final Log _log = new Log(SOCKS4aServer.class);
|
||||
|
||||
private Socket clientSock = null;
|
||||
private boolean setupCompleted = false;
|
||||
|
||||
/**
|
||||
* Create a SOCKS4a server that communicates with the client using
|
||||
* the specified socket. This method should not be invoked
|
||||
* directly: new SOCKS4aServer objects should be created by using
|
||||
* SOCKSServerFactory.createSOCSKServer(). It is assumed that the
|
||||
* SOCKS VER field has been stripped from the input stream of the
|
||||
* client socket.
|
||||
*
|
||||
* @param clientSock client socket
|
||||
*/
|
||||
public SOCKS4aServer(Socket clientSock) {
|
||||
this.clientSock = clientSock;
|
||||
}
|
||||
|
||||
public Socket getClientSocket() throws SOCKSException {
|
||||
setupServer();
|
||||
|
||||
return clientSock;
|
||||
}
|
||||
|
||||
protected void setupServer() throws SOCKSException {
|
||||
if (setupCompleted) { return; }
|
||||
|
||||
DataInputStream in;
|
||||
DataOutputStream out;
|
||||
try {
|
||||
in = new DataInputStream(clientSock.getInputStream());
|
||||
out = new DataOutputStream(clientSock.getOutputStream());
|
||||
|
||||
manageRequest(in, out);
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
|
||||
}
|
||||
|
||||
setupCompleted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* SOCKS4a request management. This method assumes that all the
|
||||
* stuff preceding or enveloping the actual request
|
||||
* has been stripped out of the input/output streams.
|
||||
*/
|
||||
private void manageRequest(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
|
||||
|
||||
int command = in.readByte() & 0xff;
|
||||
switch (command) {
|
||||
case Command.CONNECT:
|
||||
break;
|
||||
case Command.BIND:
|
||||
_log.debug("BIND command is not supported!");
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
throw new SOCKSException("BIND command not supported");
|
||||
default:
|
||||
_log.debug("unknown command in request (" + Integer.toHexString(command) + ")");
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
throw new SOCKSException("Invalid command in request");
|
||||
}
|
||||
|
||||
connPort = in.readUnsignedShort();
|
||||
if (connPort == 0) {
|
||||
_log.debug("trying to connect to TCP port 0? Dropping!");
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
throw new SOCKSException("Invalid port number in request");
|
||||
}
|
||||
|
||||
connHostName = new String("");
|
||||
boolean alreadyWarned = false;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int octet = in.readByte() & 0xff;
|
||||
connHostName += Integer.toString(octet);
|
||||
if (i != 3) {
|
||||
connHostName += ".";
|
||||
if (octet != 0 && !alreadyWarned) {
|
||||
_log.warn("IPV4 address type in request: " + connHostName + ". Is your client secure?");
|
||||
alreadyWarned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// discard user name
|
||||
readString(in);
|
||||
|
||||
// SOCKS 4a
|
||||
if (connHostName.startsWith("0.0.0.") && !connHostName.equals("0.0.0.0"))
|
||||
connHostName = readString(in);
|
||||
}
|
||||
|
||||
private String readString(DataInputStream in) throws IOException {
|
||||
StringBuffer sb = new StringBuffer(16);
|
||||
char c;
|
||||
while ((c = (char) (in.readByte() & 0xff)) != 0)
|
||||
sb.append(c);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected void confirmConnection() throws SOCKSException {
|
||||
DataInputStream in;
|
||||
DataOutputStream out;
|
||||
try {
|
||||
out = new DataOutputStream(clientSock.getOutputStream());
|
||||
|
||||
sendRequestReply(Reply.SUCCEEDED, InetAddress.getByName("127.0.0.1"), 1, out);
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the specified reply to a request of the client. Either
|
||||
* one of inetAddr or domainName can be null, depending on
|
||||
* addressType.
|
||||
*/
|
||||
private void sendRequestReply(int replyCode, InetAddress inetAddr,
|
||||
int bindPort, DataOutputStream out) throws IOException {
|
||||
ByteArrayOutputStream reps = new ByteArrayOutputStream();
|
||||
DataOutputStream dreps = new DataOutputStream(reps);
|
||||
|
||||
// Reserved byte, should be 0x00
|
||||
dreps.write(0x00);
|
||||
dreps.write(replyCode);
|
||||
dreps.writeShort(bindPort);
|
||||
dreps.write(inetAddr.getAddress());
|
||||
|
||||
byte[] reply = reps.toByteArray();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Sending request reply:\n" + HexDump.dump(reply));
|
||||
}
|
||||
|
||||
out.write(reply);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an I2PSocket that can be used to send/receive 8-bit clean data
|
||||
* to/from the destination of the SOCKS connection.
|
||||
*
|
||||
* @return an I2PSocket connected with the destination
|
||||
*/
|
||||
public I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException {
|
||||
setupServer();
|
||||
|
||||
if (connHostName == null) {
|
||||
_log.error("BUG: destination host name has not been initialized!");
|
||||
throw new SOCKSException("BUG! See the logs!");
|
||||
}
|
||||
if (connPort == 0) {
|
||||
_log.error("BUG: destination port has not been initialized!");
|
||||
throw new SOCKSException("BUG! See the logs!");
|
||||
}
|
||||
|
||||
DataOutputStream out; // for errors
|
||||
try {
|
||||
out = new DataOutputStream(clientSock.getOutputStream());
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
|
||||
}
|
||||
|
||||
// FIXME: here we should read our config file, select an
|
||||
// outproxy, and instantiate the proper socket class that
|
||||
// handles the outproxy itself (SOCKS4a, SOCKS4a, HTTP CONNECT...).
|
||||
I2PSocket destSock;
|
||||
|
||||
try {
|
||||
if (connHostName.toLowerCase().endsWith(".i2p")) {
|
||||
_log.debug("connecting to " + connHostName + "...");
|
||||
// Let's not due a new Dest for every request, huh?
|
||||
//I2PSocketManager sm = I2PSocketManagerFactory.createManager();
|
||||
//destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
|
||||
destSock = t.createI2PSocket(I2PTunnel.destFromName(connHostName));
|
||||
} else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) {
|
||||
String err = "No localhost accesses allowed through the Socks Proxy";
|
||||
_log.error(err);
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException(err);
|
||||
} else if (connPort == 80) {
|
||||
// rewrite GET line to include hostname??? or add Host: line???
|
||||
// or forward to local eepProxy (but that's a Socket not an I2PSocket)
|
||||
// use eepProxy configured outproxies?
|
||||
String err = "No handler for HTTP outproxy implemented - to: " + connHostName;
|
||||
_log.error(err);
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException(err);
|
||||
} else {
|
||||
List<String> proxies = t.getProxies(connPort);
|
||||
if (proxies == null || proxies.size() <= 0) {
|
||||
String err = "No outproxy configured for port " + connPort + " and no default configured either";
|
||||
_log.error(err);
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException(err);
|
||||
}
|
||||
int p = I2PAppContext.getGlobalContext().random().nextInt(proxies.size());
|
||||
String proxy = proxies.get(p);
|
||||
_log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "...");
|
||||
// this isn't going to work, these need to be socks outproxies so we need
|
||||
// to do a socks session to them?
|
||||
destSock = t.createI2PSocket(I2PTunnel.destFromName(proxy));
|
||||
}
|
||||
confirmConnection();
|
||||
_log.debug("connection confirmed - exchanging data...");
|
||||
} catch (DataFormatException e) {
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error in destination format");
|
||||
} catch (SocketException e) {
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
} catch (I2PException e) {
|
||||
try {
|
||||
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
}
|
||||
|
||||
return destSock;
|
||||
}
|
||||
|
||||
/*
|
||||
* Some namespaces to enclose SOCKS protocol codes
|
||||
*/
|
||||
private static class Command {
|
||||
private static final int CONNECT = 0x01;
|
||||
private static final int BIND = 0x02;
|
||||
}
|
||||
|
||||
private static class Reply {
|
||||
private static final int SUCCEEDED = 0x5a;
|
||||
private static final int CONNECTION_REFUSED = 0x5b;
|
||||
}
|
||||
}
|
@ -44,6 +44,10 @@ public class SOCKSServerFactory {
|
||||
int socksVer = in.readByte();
|
||||
|
||||
switch (socksVer) {
|
||||
case 0x04:
|
||||
// SOCKS version 4/4a
|
||||
serv = new SOCKS4aServer(s);
|
||||
break;
|
||||
case 0x05:
|
||||
// SOCKS version 5
|
||||
serv = new SOCKS5Server(s);
|
||||
|
@ -384,7 +384,7 @@ public class IndexBean {
|
||||
else if ("ircclient".equals(internalType)) return "IRC client";
|
||||
else if ("server".equals(internalType)) return "Standard server";
|
||||
else if ("httpserver".equals(internalType)) return "HTTP server";
|
||||
else if ("sockstunnel".equals(internalType)) return "SOCKS 5 proxy";
|
||||
else if ("sockstunnel".equals(internalType)) return "SOCKS 4/4a/5 proxy";
|
||||
else if ("connectclient".equals(internalType)) return "CONNECT/SSL/HTTPS proxy";
|
||||
else if ("ircserver".equals(internalType)) return "IRC server";
|
||||
else return internalType;
|
||||
|
Reference in New Issue
Block a user