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();
|
int socksVer = in.readByte();
|
||||||
|
|
||||||
switch (socksVer) {
|
switch (socksVer) {
|
||||||
|
case 0x04:
|
||||||
|
// SOCKS version 4/4a
|
||||||
|
serv = new SOCKS4aServer(s);
|
||||||
|
break;
|
||||||
case 0x05:
|
case 0x05:
|
||||||
// SOCKS version 5
|
// SOCKS version 5
|
||||||
serv = new SOCKS5Server(s);
|
serv = new SOCKS5Server(s);
|
||||||
|
@ -384,7 +384,7 @@ public class IndexBean {
|
|||||||
else if ("ircclient".equals(internalType)) return "IRC client";
|
else if ("ircclient".equals(internalType)) return "IRC client";
|
||||||
else if ("server".equals(internalType)) return "Standard server";
|
else if ("server".equals(internalType)) return "Standard server";
|
||||||
else if ("httpserver".equals(internalType)) return "HTTP 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 ("connectclient".equals(internalType)) return "CONNECT/SSL/HTTPS proxy";
|
||||||
else if ("ircserver".equals(internalType)) return "IRC server";
|
else if ("ircserver".equals(internalType)) return "IRC server";
|
||||||
else return internalType;
|
else return internalType;
|
||||||
|
@ -148,7 +148,7 @@
|
|||||||
<option value="client">Standard</option>
|
<option value="client">Standard</option>
|
||||||
<option value="httpclient">HTTP</option>
|
<option value="httpclient">HTTP</option>
|
||||||
<option value="ircclient">IRC</option>
|
<option value="ircclient">IRC</option>
|
||||||
<option value="sockstunnel">SOCKS 5</option>
|
<option value="sockstunnel">SOCKS 4/4a/5</option>
|
||||||
<option value="connectclient">CONNECT</option>
|
<option value="connectclient">CONNECT</option>
|
||||||
</select>
|
</select>
|
||||||
<input class="control" type="submit" value="Create" />
|
<input class="control" type="submit" value="Create" />
|
||||||
|
Reference in New Issue
Block a user