forked from I2P_Developers/i2p.i2p
* I2PTunnelIRCClient:
- Big refactoring into multiple class files - Allow AWAY and CAP messages - First cut at DCC support - not for SOCKS (yet)
This commit is contained in:
@@ -193,6 +193,9 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
|||||||
|
|
||||||
// no need to load the netDb with leaseSets for destinations that will never
|
// no need to load the netDb with leaseSets for destinations that will never
|
||||||
// be looked up
|
// be looked up
|
||||||
|
boolean dccEnabled = (this instanceof I2PTunnelIRCClient) &&
|
||||||
|
Boolean.valueOf(tunnel.getClientOptions().getProperty(I2PTunnelIRCClient.PROP_DCC)).booleanValue();
|
||||||
|
if (!dccEnabled)
|
||||||
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
|
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
|
||||||
|
|
||||||
boolean openNow = !Boolean.valueOf(tunnel.getClientOptions().getProperty("i2cp.delayOpen")).booleanValue();
|
boolean openNow = !Boolean.valueOf(tunnel.getClientOptions().getProperty("i2cp.delayOpen")).booleanValue();
|
||||||
|
@@ -10,7 +10,13 @@ import java.util.List;
|
|||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
import net.i2p.client.streaming.I2PSocket;
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
|
import net.i2p.data.Base32;
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.i2ptunnel.irc.DCCClientManager;
|
||||||
|
import net.i2p.i2ptunnel.irc.DCCHelper;
|
||||||
|
import net.i2p.i2ptunnel.irc.I2PTunnelDCCServer;
|
||||||
|
import net.i2p.i2ptunnel.irc.IrcInboundFilter;
|
||||||
|
import net.i2p.i2ptunnel.irc.IrcOutboundFilter;
|
||||||
import net.i2p.util.EventDispatcher;
|
import net.i2p.util.EventDispatcher;
|
||||||
import net.i2p.util.I2PAppThread;
|
import net.i2p.util.I2PAppThread;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
@@ -18,7 +24,7 @@ import net.i2p.util.Log;
|
|||||||
/**
|
/**
|
||||||
* Todo: Can we extend I2PTunnelClient instead and remove some duplicated code?
|
* Todo: Can we extend I2PTunnelClient instead and remove some duplicated code?
|
||||||
*/
|
*/
|
||||||
public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable {
|
public class I2PTunnelIRCClient extends I2PTunnelClientBase implements DCCHelper {
|
||||||
|
|
||||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||||
private static volatile long __clientId = 0;
|
private static volatile long __clientId = 0;
|
||||||
@@ -27,6 +33,14 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
|||||||
protected List<Destination> dests;
|
protected List<Destination> dests;
|
||||||
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
|
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
|
||||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||||
|
private final boolean _dccEnabled;
|
||||||
|
private I2PTunnelDCCServer _DCCServer;
|
||||||
|
private DCCClientManager _DCCClientManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
public static final String PROP_DCC = "i2ptunnel.ircclient.enableDCC";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||||
@@ -75,6 +89,9 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
|||||||
|
|
||||||
setName("IRC Client on " + tunnel.listenHost + ':' + localPort);
|
setName("IRC Client on " + tunnel.listenHost + ':' + localPort);
|
||||||
|
|
||||||
|
_dccEnabled = Boolean.valueOf(tunnel.getClientOptions().getProperty(PROP_DCC)).booleanValue();
|
||||||
|
// TODO add some prudent tunnel options (or is it too late?)
|
||||||
|
|
||||||
startRunning();
|
startRunning();
|
||||||
|
|
||||||
notifyEvent("openIRCClientResult", "ok");
|
notifyEvent("openIRCClientResult", "ok");
|
||||||
@@ -89,9 +106,9 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
|||||||
i2ps = createI2PSocket(clientDest);
|
i2ps = createI2PSocket(clientDest);
|
||||||
i2ps.setReadTimeout(readTimeout);
|
i2ps.setReadTimeout(readTimeout);
|
||||||
StringBuffer expectedPong = new StringBuffer();
|
StringBuffer expectedPong = new StringBuffer();
|
||||||
Thread in = new I2PAppThread(new IrcInboundFilter(s,i2ps, expectedPong, _log), "IRC Client " + __clientId + " in", true);
|
Thread in = new I2PAppThread(new IrcInboundFilter(s,i2ps, expectedPong, _log, this), "IRC Client " + __clientId + " in", true);
|
||||||
in.start();
|
in.start();
|
||||||
Thread out = new I2PAppThread(new IrcOutboundFilter(s,i2ps, expectedPong, _log), "IRC Client " + __clientId + " out", true);
|
Thread out = new I2PAppThread(new IrcOutboundFilter(s,i2ps, expectedPong, _log, this), "IRC Client " + __clientId + " out", true);
|
||||||
out.start();
|
out.start();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
if (_log.shouldLog(Log.ERROR))
|
if (_log.shouldLog(Log.ERROR))
|
||||||
@@ -120,388 +137,62 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
|||||||
return dests.get(index);
|
return dests.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************************************************
|
@Override
|
||||||
*
|
public boolean close(boolean forced) {
|
||||||
*/
|
synchronized(this) {
|
||||||
public static class IrcInboundFilter implements Runnable {
|
if (_DCCServer != null) {
|
||||||
|
_DCCServer.close(forced);
|
||||||
private final Socket local;
|
_DCCServer = null;
|
||||||
private final I2PSocket remote;
|
}
|
||||||
private final StringBuffer expectedPong;
|
}
|
||||||
private final Log _log;
|
return super.close(forced);
|
||||||
|
|
||||||
public IrcInboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong, Log log) {
|
|
||||||
local=_local;
|
|
||||||
remote=_remote;
|
|
||||||
expectedPong=pong;
|
|
||||||
_log = log;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run() {
|
|
||||||
// Todo: Don't use BufferedReader - IRC spec limits line length to 512 but...
|
|
||||||
BufferedReader in;
|
|
||||||
OutputStream output;
|
|
||||||
try {
|
|
||||||
in = new BufferedReader(new InputStreamReader(remote.getInputStream(), "ISO-8859-1"));
|
|
||||||
output=local.getOutputStream();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (_log.shouldLog(Log.ERROR))
|
|
||||||
_log.error("IrcInboundFilter: no streams",e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
|
||||||
_log.debug("IrcInboundFilter: Running.");
|
|
||||||
try {
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
String inmsg = in.readLine();
|
|
||||||
if(inmsg==null)
|
|
||||||
break;
|
|
||||||
if(inmsg.endsWith("\r"))
|
|
||||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
|
||||||
_log.debug("in: [" + inmsg + "]");
|
|
||||||
String outmsg = inboundFilter(inmsg, expectedPong);
|
|
||||||
if(outmsg!=null)
|
|
||||||
{
|
|
||||||
if(!inmsg.equals(outmsg)) {
|
|
||||||
if (_log.shouldLog(Log.WARN)) {
|
|
||||||
_log.warn("inbound FILTERED: "+outmsg);
|
|
||||||
_log.warn(" - inbound was: "+inmsg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (_log.shouldLog(Log.INFO))
|
|
||||||
_log.info("inbound: "+outmsg);
|
|
||||||
}
|
|
||||||
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
|
|
||||||
output.write(outmsg.getBytes("ISO-8859-1"));
|
|
||||||
// probably doesn't do much but can't hurt
|
|
||||||
output.flush();
|
|
||||||
} else {
|
|
||||||
if (_log.shouldLog(Log.WARN))
|
|
||||||
_log.warn("inbound BLOCKED: "+inmsg);
|
|
||||||
}
|
|
||||||
} catch (IOException e1) {
|
|
||||||
if (_log.shouldLog(Log.WARN))
|
|
||||||
_log.warn("IrcInboundFilter: disconnected",e1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (RuntimeException re) {
|
|
||||||
_log.error("Error filtering inbound data", re);
|
|
||||||
} finally {
|
|
||||||
try { local.close(); } catch (IOException e) {}
|
|
||||||
}
|
|
||||||
if(_log.shouldLog(Log.DEBUG))
|
|
||||||
_log.debug("IrcInboundFilter: Done.");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*************************************************************************
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class IrcOutboundFilter implements Runnable {
|
|
||||||
|
|
||||||
private final Socket local;
|
|
||||||
private final I2PSocket remote;
|
|
||||||
private final StringBuffer expectedPong;
|
|
||||||
private final Log _log;
|
|
||||||
|
|
||||||
public IrcOutboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong, Log log) {
|
|
||||||
local=_local;
|
|
||||||
remote=_remote;
|
|
||||||
expectedPong=pong;
|
|
||||||
_log = log;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
// Todo: Don't use BufferedReader - IRC spec limits line length to 512 but...
|
|
||||||
BufferedReader in;
|
|
||||||
OutputStream output;
|
|
||||||
try {
|
|
||||||
in = new BufferedReader(new InputStreamReader(local.getInputStream(), "ISO-8859-1"));
|
|
||||||
output=remote.getOutputStream();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (_log.shouldLog(Log.ERROR))
|
|
||||||
_log.error("IrcOutboundFilter: no streams",e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
|
||||||
_log.debug("IrcOutboundFilter: Running.");
|
|
||||||
try {
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
String inmsg = in.readLine();
|
|
||||||
if(inmsg==null)
|
|
||||||
break;
|
|
||||||
if(inmsg.endsWith("\r"))
|
|
||||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
|
||||||
_log.debug("out: [" + inmsg + "]");
|
|
||||||
String outmsg = outboundFilter(inmsg, expectedPong);
|
|
||||||
if(outmsg!=null)
|
|
||||||
{
|
|
||||||
if(!inmsg.equals(outmsg)) {
|
|
||||||
if (_log.shouldLog(Log.WARN)) {
|
|
||||||
_log.warn("outbound FILTERED: "+outmsg);
|
|
||||||
_log.warn(" - outbound was: "+inmsg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (_log.shouldLog(Log.INFO))
|
|
||||||
_log.info("outbound: "+outmsg);
|
|
||||||
}
|
|
||||||
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
|
|
||||||
output.write(outmsg.getBytes("ISO-8859-1"));
|
|
||||||
// save 250 ms in streaming
|
|
||||||
output.flush();
|
|
||||||
} else {
|
|
||||||
if (_log.shouldLog(Log.WARN))
|
|
||||||
_log.warn("outbound BLOCKED: "+"\""+inmsg+"\"");
|
|
||||||
}
|
|
||||||
} catch (IOException e1) {
|
|
||||||
if (_log.shouldLog(Log.WARN))
|
|
||||||
_log.warn("IrcOutboundFilter: disconnected",e1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (RuntimeException re) {
|
|
||||||
_log.error("Error filtering outbound data", re);
|
|
||||||
} finally {
|
|
||||||
try { remote.close(); } catch (IOException e) {}
|
|
||||||
}
|
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
|
||||||
_log.debug("IrcOutboundFilter: Done.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*************************************************************************
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static String inboundFilter(String s, StringBuffer expectedPong) {
|
|
||||||
|
|
||||||
String field[]=s.split(" ",4);
|
|
||||||
String command;
|
|
||||||
int idx=0;
|
|
||||||
final String[] allowedCommands =
|
|
||||||
{
|
|
||||||
// "NOTICE", // can contain CTCP
|
|
||||||
//"PING",
|
|
||||||
//"PONG",
|
|
||||||
"MODE",
|
|
||||||
"JOIN",
|
|
||||||
"NICK",
|
|
||||||
"QUIT",
|
|
||||||
"PART",
|
|
||||||
"WALLOPS",
|
|
||||||
"ERROR",
|
|
||||||
"KICK",
|
|
||||||
"H", // "hide operator status" (after kicking an op)
|
|
||||||
"TOPIC"
|
|
||||||
};
|
|
||||||
|
|
||||||
if(field[0].charAt(0)==':')
|
|
||||||
idx++;
|
|
||||||
|
|
||||||
try { command = field[idx++]; }
|
|
||||||
catch (IndexOutOfBoundsException ioobe) // wtf, server sent borked command?
|
|
||||||
{
|
|
||||||
//_log.warn("Dropping defective message: index out of bounds while extracting command.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
idx++; //skip victim
|
|
||||||
|
|
||||||
// Allow numerical responses
|
|
||||||
try {
|
|
||||||
new Integer(command);
|
|
||||||
return s;
|
|
||||||
} catch(NumberFormatException nfe){}
|
|
||||||
|
|
||||||
|
|
||||||
if ("PING".equalsIgnoreCase(command))
|
|
||||||
return "PING 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
|
|
||||||
if ("PONG".equalsIgnoreCase(command)) {
|
|
||||||
// Turn the received ":irc.freshcoffee.i2p PONG irc.freshcoffee.i2p :127.0.0.1"
|
|
||||||
// into ":127.0.0.1 PONG 127.0.0.1 " so that the caller can append the client's extra parameter
|
|
||||||
// though, does 127.0.0.1 work for irc clients connecting remotely? and for all of them? sure would
|
|
||||||
// be great if irc clients actually followed the RFCs here, but i guess thats too much to ask.
|
|
||||||
// If we haven't PINGed them, or the PING we sent isn't something we know how to filter, this
|
|
||||||
// is blank.
|
|
||||||
//
|
//
|
||||||
// String pong = expectedPong.length() > 0 ? expectedPong.toString() : null;
|
// Start of the DCCHelper interface
|
||||||
// If we aren't going to rewrite it, pass it through
|
|
||||||
String pong = expectedPong.length() > 0 ? expectedPong.toString() : s;
|
|
||||||
expectedPong.setLength(0);
|
|
||||||
return pong;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow all allowedCommands
|
|
||||||
for(int i=0;i<allowedCommands.length;i++) {
|
|
||||||
if(allowedCommands[i].equalsIgnoreCase(command))
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow PRIVMSG, but block CTCP.
|
|
||||||
if("PRIVMSG".equalsIgnoreCase(command) || "NOTICE".equalsIgnoreCase(command))
|
|
||||||
{
|
|
||||||
String msg;
|
|
||||||
msg = field[idx++];
|
|
||||||
|
|
||||||
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
|
|
||||||
{
|
|
||||||
// CTCP
|
|
||||||
msg=msg.substring(2);
|
|
||||||
if(msg.startsWith("ACTION ")) {
|
|
||||||
// /me says hello
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
return null; // Block all other ctcp
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block the rest
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String outboundFilter(String s, StringBuffer expectedPong) {
|
|
||||||
|
|
||||||
String field[]=s.split(" ",3);
|
|
||||||
String command;
|
|
||||||
final String[] allowedCommands =
|
|
||||||
{
|
|
||||||
// "NOTICE", // can contain CTCP
|
|
||||||
"MODE",
|
|
||||||
"JOIN",
|
|
||||||
"NICK",
|
|
||||||
"WHO",
|
|
||||||
"WHOIS",
|
|
||||||
"LIST",
|
|
||||||
"NAMES",
|
|
||||||
"NICK",
|
|
||||||
// "QUIT", // replace with a filtered QUIT to hide client quit messages
|
|
||||||
"SILENCE",
|
|
||||||
"MAP", // seems safe enough, the ircd should protect themselves though
|
|
||||||
// "PART", // replace with filtered PART to hide client part messages
|
|
||||||
"OPER",
|
|
||||||
// "PONG", // replaced with a filtered PING/PONG since some clients send the server IP (thanks aardvax!)
|
|
||||||
// "PING",
|
|
||||||
"KICK",
|
|
||||||
"HELPME",
|
|
||||||
"RULES",
|
|
||||||
"TOPIC",
|
|
||||||
"ISON", // jIRCii uses this for a ping (response is 303)
|
|
||||||
"INVITE"
|
|
||||||
};
|
|
||||||
|
|
||||||
if(field[0].length()==0)
|
|
||||||
return null; // W T F?
|
|
||||||
|
|
||||||
|
|
||||||
if(field[0].charAt(0)==':')
|
|
||||||
return null; // wtf
|
|
||||||
|
|
||||||
command = field[0].toUpperCase();
|
|
||||||
|
|
||||||
if ("PING".equals(command)) {
|
|
||||||
// Most clients just send a PING and are happy with any old PONG. Others,
|
|
||||||
// like BitchX, actually expect certain behavior. It sends two different pings:
|
|
||||||
// "PING :irc.freshcoffee.i2p" and "PING 1234567890 127.0.0.1" (where the IP is the proxy)
|
|
||||||
// the PONG to the former seems to be "PONG 127.0.0.1", while the PONG to the later is
|
|
||||||
// ":irc.freshcoffee.i2p PONG irc.freshcoffe.i2p :1234567890".
|
|
||||||
// We don't want to send them our proxy's IP address, so we need to rewrite the PING
|
|
||||||
// sent to the server, but when we get a PONG back, use what we expected, rather than
|
|
||||||
// what they sent.
|
|
||||||
//
|
//
|
||||||
// Yuck.
|
|
||||||
|
|
||||||
String rv = null;
|
public boolean isEnabled() {
|
||||||
expectedPong.setLength(0);
|
return _dccEnabled;
|
||||||
if (field.length == 1) { // PING
|
|
||||||
rv = "PING";
|
|
||||||
// If we aren't rewriting the PING don't rewrite the PONG
|
|
||||||
// expectedPong.append("PONG 127.0.0.1");
|
|
||||||
} else if (field.length == 2) { // PING nonce
|
|
||||||
rv = "PING " + field[1];
|
|
||||||
// If we aren't rewriting the PING don't rewrite the PONG
|
|
||||||
// expectedPong.append("PONG ").append(field[1]);
|
|
||||||
} else if (field.length == 3) { // PING nonce serverLocation
|
|
||||||
rv = "PING " + field[1];
|
|
||||||
expectedPong.append("PONG ").append(field[2]).append(" :").append(field[1]); // PONG serverLocation nonce
|
|
||||||
} else {
|
|
||||||
//if (_log.shouldLog(Log.ERROR))
|
|
||||||
// _log.error("IRC client sent a PING we don't understand, filtering it (\"" + s + "\")");
|
|
||||||
rv = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (_log.shouldLog(Log.WARN))
|
public String getB32Hostname() {
|
||||||
// _log.warn("sending ping [" + rv + "], waiting for [" + expectedPong + "] orig was [" + s + "]");
|
return Base32.encode(sockMgr.getSession().getMyDestination().calculateHash().getData()) + ".b32.i2p";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int newOutgoing(byte[] ip, int port, String type) {
|
||||||
|
I2PTunnelDCCServer server;
|
||||||
|
synchronized(this) {
|
||||||
|
if (_DCCServer == null) {
|
||||||
|
if (_log.shouldLog(Log.INFO))
|
||||||
|
_log.info("Starting DCC Server");
|
||||||
|
_DCCServer = new I2PTunnelDCCServer(sockMgr, l, this, getTunnel());
|
||||||
|
// TODO add some prudent tunnel options (or is it too late?)
|
||||||
|
_DCCServer.startRunning();
|
||||||
|
}
|
||||||
|
server = _DCCServer;
|
||||||
|
}
|
||||||
|
int rv = server.newOutgoing(ip, port, type);
|
||||||
|
if (_log.shouldLog(Log.INFO))
|
||||||
|
_log.info("New outgoing " + type + ' ' + port + " returns " + rv);
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
if ("PONG".equals(command))
|
|
||||||
return "PONG 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
|
|
||||||
|
|
||||||
// Allow all allowedCommands
|
public int newIncoming(String b32, int port, String type) {
|
||||||
for(int i=0;i<allowedCommands.length;i++)
|
DCCClientManager tracker;
|
||||||
{
|
synchronized(this) {
|
||||||
if(allowedCommands[i].equals(command))
|
if (_DCCClientManager == null) {
|
||||||
return s;
|
if (_log.shouldLog(Log.INFO))
|
||||||
|
_log.info("Starting DCC Client");
|
||||||
|
_DCCClientManager = new DCCClientManager(sockMgr, l, this, getTunnel());
|
||||||
}
|
}
|
||||||
|
tracker = _DCCClientManager;
|
||||||
// mIRC sends "NOTICE user :DCC Send file (IP)"
|
|
||||||
// in addition to the CTCP version
|
|
||||||
if("NOTICE".equals(command))
|
|
||||||
{
|
|
||||||
String msg = field[2];
|
|
||||||
if(msg.startsWith(":DCC "))
|
|
||||||
return null;
|
|
||||||
// fall through
|
|
||||||
}
|
}
|
||||||
|
// The tracker starts our client
|
||||||
// Allow PRIVMSG, but block CTCP (except ACTION).
|
int rv = tracker.newIncoming(b32, port, type);
|
||||||
if("PRIVMSG".equals(command) || "NOTICE".equals(command))
|
if (_log.shouldLog(Log.INFO))
|
||||||
{
|
_log.info("New incoming " + type + ' ' + b32 + ' ' + port + " returns " + rv);
|
||||||
String msg;
|
return rv;
|
||||||
msg = field[2];
|
|
||||||
|
|
||||||
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
|
|
||||||
{
|
|
||||||
// CTCP
|
|
||||||
msg=msg.substring(2);
|
|
||||||
if(msg.startsWith("ACTION ")) {
|
|
||||||
// /me says hello
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
return null; // Block all other ctcp
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
if("USER".equals(command)) {
|
|
||||||
int idx = field[2].lastIndexOf(":");
|
|
||||||
if(idx<0)
|
|
||||||
return "USER user hostname localhost :realname";
|
|
||||||
String realname = field[2].substring(idx+1);
|
|
||||||
String ret = "USER "+field[1]+" hostname localhost :"+realname;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("PART".equals(command)) {
|
|
||||||
// hide client message
|
|
||||||
return "PART " + field[1] + " :leaving";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("QUIT".equals(command)) {
|
|
||||||
return "QUIT :leaving";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block the rest
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -47,7 +47,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
|||||||
protected int remotePort;
|
protected int remotePort;
|
||||||
private boolean _usePool;
|
private boolean _usePool;
|
||||||
|
|
||||||
private Logging l;
|
protected Logging l;
|
||||||
|
|
||||||
private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
|
private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
|
||||||
/** default timeout to 3 minutes - override if desired */
|
/** default timeout to 3 minutes - override if desired */
|
||||||
@@ -69,10 +69,13 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
|||||||
protected boolean bidir = false;
|
protected boolean bidir = false;
|
||||||
private ThreadPoolExecutor _executor;
|
private ThreadPoolExecutor _executor;
|
||||||
|
|
||||||
|
/** unused? port should always be specified */
|
||||||
private int DEFAULT_LOCALPORT = 4488;
|
private int DEFAULT_LOCALPORT = 4488;
|
||||||
protected int localPort = DEFAULT_LOCALPORT;
|
protected int localPort = DEFAULT_LOCALPORT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param privData Base64-encoded private key data,
|
||||||
|
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
|
||||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||||
* badly that we cant create a socketManager
|
* badly that we cant create a socketManager
|
||||||
*/
|
*/
|
||||||
@@ -84,6 +87,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param privkey file containing the private key data,
|
||||||
|
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
|
||||||
|
* @param privkeyname the name of the privKey file, not clear why we need this too
|
||||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||||
* badly that we cant create a socketManager
|
* badly that we cant create a socketManager
|
||||||
*/
|
*/
|
||||||
@@ -105,6 +111,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param privData stream containing the private key data,
|
||||||
|
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
|
||||||
|
* @param privkeyname the name of the privKey file, not clear why we need this too
|
||||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||||
* badly that we cant create a socketManager
|
* badly that we cant create a socketManager
|
||||||
*/
|
*/
|
||||||
@@ -114,10 +123,28 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
|||||||
init(host, port, privData, privkeyname, l);
|
init(host, port, privData, privkeyname, l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sktMgr the existing socket manager
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
public I2PTunnelServer(InetAddress host, int port, I2PSocketManager sktMgr,
|
||||||
|
Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||||
|
super("Server at " + host + ':' + port, notifyThis, tunnel);
|
||||||
|
this.l = l;
|
||||||
|
this.remoteHost = host;
|
||||||
|
this.remotePort = port;
|
||||||
|
_log = tunnel.getContext().logManager().getLog(getClass());
|
||||||
|
sockMgr = sktMgr;
|
||||||
|
open = true;
|
||||||
|
}
|
||||||
|
|
||||||
private static final int RETRY_DELAY = 20*1000;
|
private static final int RETRY_DELAY = 20*1000;
|
||||||
private static final int MAX_RETRIES = 4;
|
private static final int MAX_RETRIES = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param privData stream containing the private key data,
|
||||||
|
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
|
||||||
|
* @param privkeyname the name of the privKey file, not clear why we need this too
|
||||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||||
* badly that we cant create a socketManager
|
* badly that we cant create a socketManager
|
||||||
*/
|
*/
|
||||||
|
@@ -0,0 +1,105 @@
|
|||||||
|
package net.i2p.i2ptunnel.irc;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import net.i2p.client.streaming.I2PSocketManager;
|
||||||
|
import net.i2p.i2ptunnel.I2PTunnel;
|
||||||
|
import net.i2p.i2ptunnel.Logging;
|
||||||
|
import net.i2p.util.EventDispatcher;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start, track, and expire the I2PTunnelDCCClients.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
*
|
||||||
|
* <--- I2PTunnelDCCServer <--------------- I2PTunnelDCCClient <----
|
||||||
|
* originating responding
|
||||||
|
* chat client chat client
|
||||||
|
* ---> I2PTunnelIRCClient --> IRC server --> I2TunnelIRCClient ----->
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
public class DCCClientManager {
|
||||||
|
|
||||||
|
private final I2PSocketManager sockMgr;
|
||||||
|
private final EventDispatcher _dispatch;
|
||||||
|
private final Logging l;
|
||||||
|
private final I2PTunnel _tunnel;
|
||||||
|
private final Log _log;
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<Integer, I2PAddress> _incoming;
|
||||||
|
// list of client tunnels?
|
||||||
|
private static long _id;
|
||||||
|
|
||||||
|
private static final int MAX_INCOMING_PENDING = 10;
|
||||||
|
private static final int MAX_INCOMING_ACTIVE = 10;
|
||||||
|
private static final long INBOUND_EXPIRE = 30*60*1000;
|
||||||
|
|
||||||
|
public DCCClientManager(I2PSocketManager sktMgr, Logging logging,
|
||||||
|
EventDispatcher dispatch, I2PTunnel tunnel) {
|
||||||
|
sockMgr = sktMgr;
|
||||||
|
l = logging;
|
||||||
|
_dispatch = dispatch;
|
||||||
|
_tunnel = tunnel;
|
||||||
|
_log = tunnel.getContext().logManager().getLog(DCCClientManager.class);
|
||||||
|
_incoming = new ConcurrentHashMap(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An incoming DCC request
|
||||||
|
*
|
||||||
|
* @param b32 remote dcc server address
|
||||||
|
* @param port remote dcc server port
|
||||||
|
* @param type ignored
|
||||||
|
* @return local server port or -1 on error
|
||||||
|
*/
|
||||||
|
public int newIncoming(String b32, int port, String type) {
|
||||||
|
expireInbound();
|
||||||
|
if (_incoming.size() >= MAX_INCOMING_PENDING) {
|
||||||
|
_log.error("Too many incoming DCC, max is " + MAX_INCOMING_PENDING);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
I2PAddress client = new I2PAddress(b32, port, _tunnel.getContext().clock().now() + INBOUND_EXPIRE);
|
||||||
|
try {
|
||||||
|
// Transparent tunnel used for all types...
|
||||||
|
// Do we need to do any filtering for chat?
|
||||||
|
I2PTunnelDCCClient cTunnel = new I2PTunnelDCCClient(b32, port, l, sockMgr,
|
||||||
|
_dispatch, _tunnel, ++_id);
|
||||||
|
int lport = cTunnel.getLocalPort();
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Opened client tunnel at port " + lport +
|
||||||
|
" pointing to " + b32 + ':' + port);
|
||||||
|
_incoming.put(Integer.valueOf(lport), client);
|
||||||
|
return lport;
|
||||||
|
} catch (IllegalArgumentException uhe) {
|
||||||
|
l.log("Could not find listen host to bind to [" + _tunnel.host + "]");
|
||||||
|
_log.error("Error finding host to bind", uhe);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expireInbound() {
|
||||||
|
for (Iterator<I2PAddress> iter = _incoming.values().iterator(); iter.hasNext(); ) {
|
||||||
|
I2PAddress a = iter.next();
|
||||||
|
if (a.expire < _tunnel.getContext().clock().now())
|
||||||
|
iter.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class I2PAddress {
|
||||||
|
public final String dest;
|
||||||
|
public final int port;
|
||||||
|
public final long expire;
|
||||||
|
|
||||||
|
public I2PAddress(String b32, int p, long exp) {
|
||||||
|
dest = b32;
|
||||||
|
port = p;
|
||||||
|
expire = exp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/DCCHelper.java
Normal file
37
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/DCCHelper.java
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package net.i2p.i2ptunnel.irc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks to create and maintain DCC client and server tunnels
|
||||||
|
*
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
public interface DCCHelper {
|
||||||
|
|
||||||
|
public boolean isEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String to put in the outgoing DCC
|
||||||
|
*/
|
||||||
|
public String getB32Hostname();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An outgoing DCC request
|
||||||
|
*
|
||||||
|
* @param ip local irc client IP
|
||||||
|
* @param port local irc client port
|
||||||
|
* @param type string
|
||||||
|
* @return i2p port or -1 on error
|
||||||
|
*/
|
||||||
|
public int newOutgoing(byte[] ip, int port, String type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An incoming DCC request
|
||||||
|
*
|
||||||
|
* @param b32 remote dcc server address
|
||||||
|
* @param port remote dcc server port
|
||||||
|
* @param type string
|
||||||
|
* @return local server port or -1 on error
|
||||||
|
*/
|
||||||
|
public int newIncoming(String b32, int port, String type);
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,77 @@
|
|||||||
|
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||||
|
* (c) 2003 - 2004 mihi
|
||||||
|
*/
|
||||||
|
package net.i2p.i2ptunnel.irc;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
|
import net.i2p.client.streaming.I2PSocketManager;
|
||||||
|
import net.i2p.client.streaming.I2PSocketOptions;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.i2ptunnel.I2PTunnel;
|
||||||
|
import net.i2p.i2ptunnel.I2PTunnelClientBase;
|
||||||
|
import net.i2p.i2ptunnel.I2PTunnelRunner;
|
||||||
|
import net.i2p.i2ptunnel.Logging;
|
||||||
|
import net.i2p.util.EventDispatcher;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A standard client, using an existing socket manager.
|
||||||
|
* Targets a single destination and port.
|
||||||
|
* Naming resolution is delayed until connect time.
|
||||||
|
*
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
public class I2PTunnelDCCClient extends I2PTunnelClientBase {
|
||||||
|
|
||||||
|
// delay resolution until connect time
|
||||||
|
private final String _dest;
|
||||||
|
private final int _remotePort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dest the target, presumably b32
|
||||||
|
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||||
|
* valid config to contact the router
|
||||||
|
*/
|
||||||
|
public I2PTunnelDCCClient(String dest, int remotePort, Logging l,
|
||||||
|
I2PSocketManager sktMgr, EventDispatcher notifyThis,
|
||||||
|
I2PTunnel tunnel, long clientId) throws IllegalArgumentException {
|
||||||
|
super(0, l, sktMgr, tunnel, notifyThis, clientId);
|
||||||
|
_dest = dest;
|
||||||
|
_remotePort = remotePort;
|
||||||
|
|
||||||
|
setName("DCC send -> " + dest + ':' + remotePort);
|
||||||
|
|
||||||
|
startRunning();
|
||||||
|
|
||||||
|
notifyEvent("openClientResult", "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void clientConnectionRun(Socket s) {
|
||||||
|
I2PSocket i2ps = null;
|
||||||
|
if (_log.shouldLog(Log.INFO))
|
||||||
|
_log.info("Opening DCC connection to " + _dest + ':' + _remotePort);
|
||||||
|
Destination dest = _context.namingService().lookup(_dest);
|
||||||
|
if (dest == null) {
|
||||||
|
_log.error("Could not find leaseset for DCC connection to " + _dest + ':' + _remotePort);
|
||||||
|
closeSocket(s);
|
||||||
|
// shutdown?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PSocketOptions opts = sockMgr.buildOptions();
|
||||||
|
opts.setPort(_remotePort);
|
||||||
|
try {
|
||||||
|
i2ps = createI2PSocket(dest, opts);
|
||||||
|
new I2PTunnelRunner(s, i2ps, sockLock, null, mySockets);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
_log.error("Could not make DCC connection to " + _dest + ':' + _remotePort, ex);
|
||||||
|
closeSocket(s);
|
||||||
|
if (i2ps != null) {
|
||||||
|
try { i2ps.close(); } catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,173 @@
|
|||||||
|
package net.i2p.i2ptunnel.irc;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
|
import net.i2p.client.streaming.I2PSocketManager;
|
||||||
|
import net.i2p.i2ptunnel.I2PTunnel;
|
||||||
|
import net.i2p.i2ptunnel.I2PTunnelRunner;
|
||||||
|
import net.i2p.i2ptunnel.I2PTunnelServer;
|
||||||
|
import net.i2p.i2ptunnel.Logging;
|
||||||
|
import net.i2p.util.EventDispatcher;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A standard server that only answers for registered ports,
|
||||||
|
* and each port can only be used once.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
*
|
||||||
|
* <--- I2PTunnelDCCServer <--------------- I2PTunnelDCCClient <----
|
||||||
|
* originating responding
|
||||||
|
* chat client chat client
|
||||||
|
* ---> I2PTunnelIRCClient --> IRC server --> I2TunnelIRCClient ----->
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
public class I2PTunnelDCCServer extends I2PTunnelServer {
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<Integer, LocalAddress> _outgoing;
|
||||||
|
// list of client tunnels?
|
||||||
|
private static long _id;
|
||||||
|
|
||||||
|
/** just to keep super() happy */
|
||||||
|
private static final InetAddress DUMMY;
|
||||||
|
static {
|
||||||
|
InetAddress dummy = null;
|
||||||
|
try {
|
||||||
|
dummy = InetAddress.getByAddress(new byte[4]);
|
||||||
|
} catch (UnknownHostException uhe) {}
|
||||||
|
DUMMY = dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int MIN_I2P_PORT = 1;
|
||||||
|
private static final int MAX_I2P_PORT = 65535;
|
||||||
|
private static final int MAX_OUTGOING_PENDING = 20;
|
||||||
|
private static final int MAX_OUTGOING_ACTIVE = 20;
|
||||||
|
private static final long OUTBOUND_EXPIRE = 30*60*1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There's no support for unsolicited incoming I2P connections,
|
||||||
|
* so there's no server host or port parameters.
|
||||||
|
*
|
||||||
|
* @param sktMgr an existing socket manager
|
||||||
|
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||||
|
* valid config to contact the router
|
||||||
|
*/
|
||||||
|
public I2PTunnelDCCServer(I2PSocketManager sktMgr, Logging l,
|
||||||
|
EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||||
|
super(DUMMY, 0, sktMgr, l, notifyThis, tunnel);
|
||||||
|
_outgoing = new ConcurrentHashMap(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An incoming DCC connection, only accept for a known port.
|
||||||
|
* Passed through without filtering.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void blockingHandle(I2PSocket socket) {
|
||||||
|
if (_log.shouldLog(Log.INFO))
|
||||||
|
_log.info("Incoming connection to '" + toString() + "' from: " + socket.getPeerDestination().calculateHash().toBase64());
|
||||||
|
|
||||||
|
try {
|
||||||
|
expireOutbound();
|
||||||
|
int myPort = socket.getLocalPort();
|
||||||
|
// TODO remove, add to active
|
||||||
|
LocalAddress local = _outgoing.get(Integer.valueOf(myPort));
|
||||||
|
if (local == null) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Incoming DCC connection for unknown port " + myPort);
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException ioe) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Incoming DCC connection for I2P port " + myPort +
|
||||||
|
" sending to " + local.ia + ':' + local.port);
|
||||||
|
Socket s = new Socket(local.ia, local.port);
|
||||||
|
new I2PTunnelRunner(s, socket, slock, null, null);
|
||||||
|
} catch (SocketException ex) {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException ioe) {}
|
||||||
|
if (_log.shouldLog(Log.ERROR))
|
||||||
|
_log.error("Error connecting to server " + remoteHost + ':' + remotePort, ex);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
_log.error("Error while waiting for I2PConnections", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An outgoing DCC request
|
||||||
|
*
|
||||||
|
* @param ip local irc client IP
|
||||||
|
* @param port local irc client port
|
||||||
|
* @param type ignored
|
||||||
|
* @return i2p port or -1 on error
|
||||||
|
*/
|
||||||
|
public int newOutgoing(byte[] ip, int port, String type) {
|
||||||
|
expireOutbound();
|
||||||
|
if (_outgoing.size() >= MAX_OUTGOING_PENDING) {
|
||||||
|
_log.error("Too many outgoing DCC, max is " + MAX_OUTGOING_PENDING);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
InetAddress ia;
|
||||||
|
try {
|
||||||
|
ia = InetAddress.getByAddress(ip);
|
||||||
|
} catch (UnknownHostException uhe) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
LocalAddress client = new LocalAddress(ia, port, getTunnel().getContext().clock().now() + OUTBOUND_EXPIRE);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
int iport = MIN_I2P_PORT + getTunnel().getContext().random().nextInt(1 + MAX_I2P_PORT - MIN_I2P_PORT);
|
||||||
|
LocalAddress old = _outgoing.putIfAbsent(Integer.valueOf(iport), client);
|
||||||
|
if (old != null)
|
||||||
|
continue;
|
||||||
|
// TODO expire in a few minutes
|
||||||
|
return iport;
|
||||||
|
}
|
||||||
|
// couldn't find an unused i2p port
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InetAddress getListenHost(Logging l) {
|
||||||
|
try {
|
||||||
|
return InetAddress.getByName(getTunnel().listenHost);
|
||||||
|
} catch (UnknownHostException uhe) {
|
||||||
|
l.log("Could not find listen host to bind to [" + getTunnel().host + "]");
|
||||||
|
_log.error("Error finding host to bind", uhe);
|
||||||
|
notifyEvent("openBaseClientResult", "error");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expireOutbound() {
|
||||||
|
for (Iterator<LocalAddress> iter = _outgoing.values().iterator(); iter.hasNext(); ) {
|
||||||
|
LocalAddress a = iter.next();
|
||||||
|
if (a.expire < getTunnel().getContext().clock().now())
|
||||||
|
iter.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LocalAddress {
|
||||||
|
public final InetAddress ia;
|
||||||
|
public final int port;
|
||||||
|
public final long expire;
|
||||||
|
|
||||||
|
public LocalAddress(InetAddress a, int p, long exp) {
|
||||||
|
ia = a;
|
||||||
|
port = p;
|
||||||
|
expire = exp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
428
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/IRCFilter.java
Normal file
428
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/IRCFilter.java
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
package net.i2p.i2ptunnel.irc;
|
||||||
|
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static methods to filter individual lines.
|
||||||
|
* Moved from I2PTunnelIRCClient.java
|
||||||
|
*
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
abstract class IRCFilter {
|
||||||
|
|
||||||
|
private static final boolean ALLOW_ALL_DCC_IN = false;
|
||||||
|
private static final boolean ALLOW_ALL_DCC_OUT = false;
|
||||||
|
/** does not override DCC handling */
|
||||||
|
private static final boolean ALLOW_ALL_CTCP_IN = false;
|
||||||
|
/** does not override DCC handling */
|
||||||
|
private static final boolean ALLOW_ALL_CTCP_OUT = false;
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*
|
||||||
|
* Modify or filter a single inbound line.
|
||||||
|
*
|
||||||
|
* @param helper may be null
|
||||||
|
* @return the original or modified line, or null if it should be dropped.
|
||||||
|
*/
|
||||||
|
public static String inboundFilter(String s, StringBuffer expectedPong, DCCHelper helper) {
|
||||||
|
|
||||||
|
String field[]=s.split(" ",4);
|
||||||
|
String command;
|
||||||
|
int idx=0;
|
||||||
|
final String[] allowedCommands =
|
||||||
|
{
|
||||||
|
// "NOTICE", // can contain CTCP
|
||||||
|
//"PING",
|
||||||
|
//"PONG",
|
||||||
|
"MODE",
|
||||||
|
"JOIN",
|
||||||
|
"NICK",
|
||||||
|
"QUIT",
|
||||||
|
"PART",
|
||||||
|
"WALLOPS",
|
||||||
|
"ERROR",
|
||||||
|
"KICK",
|
||||||
|
"H", // "hide operator status" (after kicking an op)
|
||||||
|
"TOPIC",
|
||||||
|
// http://tools.ietf.org/html/draft-mitchell-irc-capabilities-01
|
||||||
|
"CAP"
|
||||||
|
};
|
||||||
|
|
||||||
|
if(field[0].charAt(0)==':')
|
||||||
|
idx++;
|
||||||
|
|
||||||
|
try { command = field[idx++]; }
|
||||||
|
catch (IndexOutOfBoundsException ioobe) // wtf, server sent borked command?
|
||||||
|
{
|
||||||
|
//_log.warn("Dropping defective message: index out of bounds while extracting command.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx++; //skip victim
|
||||||
|
|
||||||
|
// Allow numerical responses
|
||||||
|
try {
|
||||||
|
new Integer(command);
|
||||||
|
return s;
|
||||||
|
} catch(NumberFormatException nfe){}
|
||||||
|
|
||||||
|
|
||||||
|
if ("PING".equalsIgnoreCase(command))
|
||||||
|
return "PING 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
|
||||||
|
if ("PONG".equalsIgnoreCase(command)) {
|
||||||
|
// Turn the received ":irc.freshcoffee.i2p PONG irc.freshcoffee.i2p :127.0.0.1"
|
||||||
|
// into ":127.0.0.1 PONG 127.0.0.1 " so that the caller can append the client's extra parameter
|
||||||
|
// though, does 127.0.0.1 work for irc clients connecting remotely? and for all of them? sure would
|
||||||
|
// be great if irc clients actually followed the RFCs here, but i guess thats too much to ask.
|
||||||
|
// If we haven't PINGed them, or the PING we sent isn't something we know how to filter, this
|
||||||
|
// is blank.
|
||||||
|
//
|
||||||
|
// String pong = expectedPong.length() > 0 ? expectedPong.toString() : null;
|
||||||
|
// If we aren't going to rewrite it, pass it through
|
||||||
|
String pong = expectedPong.length() > 0 ? expectedPong.toString() : s;
|
||||||
|
expectedPong.setLength(0);
|
||||||
|
return pong;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow all allowedCommands
|
||||||
|
for(int i=0;i<allowedCommands.length;i++) {
|
||||||
|
if(allowedCommands[i].equalsIgnoreCase(command))
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow PRIVMSG, but block CTCP.
|
||||||
|
if("PRIVMSG".equalsIgnoreCase(command) || "NOTICE".equalsIgnoreCase(command))
|
||||||
|
{
|
||||||
|
String msg;
|
||||||
|
msg = field[idx++];
|
||||||
|
|
||||||
|
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
|
||||||
|
{
|
||||||
|
// CTCP
|
||||||
|
|
||||||
|
// don't even try to parse multiple CTCP in the same message
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < msg.length(); i++) {
|
||||||
|
if (msg.charAt(i) == 0x01)
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (count != 2)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
msg=msg.substring(2);
|
||||||
|
if(msg.startsWith("ACTION ")) {
|
||||||
|
// /me says hello
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
if (msg.startsWith("DCC ")) {
|
||||||
|
StringBuilder buf = new StringBuilder(128);
|
||||||
|
for (int i = 0; i <= idx - 2; i++) {
|
||||||
|
buf.append(field[i]).append(' ');
|
||||||
|
}
|
||||||
|
buf.append(":\001DCC ");
|
||||||
|
return filterDCCIn(buf.toString(), msg.substring(4), helper);
|
||||||
|
}
|
||||||
|
if (ALLOW_ALL_CTCP_IN)
|
||||||
|
return s;
|
||||||
|
return null; // Block all other ctcp
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block the rest
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*
|
||||||
|
* Modify or filter a single outbound line.
|
||||||
|
*
|
||||||
|
* @param helper may be null
|
||||||
|
* @return the original or modified line, or null if it should be dropped.
|
||||||
|
*/
|
||||||
|
public static String outboundFilter(String s, StringBuffer expectedPong, DCCHelper helper) {
|
||||||
|
|
||||||
|
String field[]=s.split(" ",3);
|
||||||
|
String command;
|
||||||
|
final String[] allowedCommands =
|
||||||
|
{
|
||||||
|
// "NOTICE", // can contain CTCP
|
||||||
|
"MODE",
|
||||||
|
"JOIN",
|
||||||
|
"NICK",
|
||||||
|
"WHO",
|
||||||
|
"WHOIS",
|
||||||
|
"LIST",
|
||||||
|
"NAMES",
|
||||||
|
"NICK",
|
||||||
|
// "QUIT", // replace with a filtered QUIT to hide client quit messages
|
||||||
|
"SILENCE",
|
||||||
|
"MAP", // seems safe enough, the ircd should protect themselves though
|
||||||
|
// "PART", // replace with filtered PART to hide client part messages
|
||||||
|
"OPER",
|
||||||
|
// "PONG", // replaced with a filtered PING/PONG since some clients send the server IP (thanks aardvax!)
|
||||||
|
// "PING",
|
||||||
|
"KICK",
|
||||||
|
"HELPME",
|
||||||
|
"RULES",
|
||||||
|
"TOPIC",
|
||||||
|
"ISON", // jIRCii uses this for a ping (response is 303)
|
||||||
|
"INVITE",
|
||||||
|
"AWAY", // should be harmless
|
||||||
|
// http://tools.ietf.org/html/draft-mitchell-irc-capabilities-01
|
||||||
|
"CAP"
|
||||||
|
};
|
||||||
|
|
||||||
|
if(field[0].length()==0)
|
||||||
|
return null; // W T F?
|
||||||
|
|
||||||
|
|
||||||
|
if(field[0].charAt(0)==':')
|
||||||
|
return null; // wtf
|
||||||
|
|
||||||
|
command = field[0].toUpperCase();
|
||||||
|
|
||||||
|
if ("PING".equals(command)) {
|
||||||
|
// Most clients just send a PING and are happy with any old PONG. Others,
|
||||||
|
// like BitchX, actually expect certain behavior. It sends two different pings:
|
||||||
|
// "PING :irc.freshcoffee.i2p" and "PING 1234567890 127.0.0.1" (where the IP is the proxy)
|
||||||
|
// the PONG to the former seems to be "PONG 127.0.0.1", while the PONG to the later is
|
||||||
|
// ":irc.freshcoffee.i2p PONG irc.freshcoffe.i2p :1234567890".
|
||||||
|
// We don't want to send them our proxy's IP address, so we need to rewrite the PING
|
||||||
|
// sent to the server, but when we get a PONG back, use what we expected, rather than
|
||||||
|
// what they sent.
|
||||||
|
//
|
||||||
|
// Yuck.
|
||||||
|
|
||||||
|
String rv = null;
|
||||||
|
expectedPong.setLength(0);
|
||||||
|
if (field.length == 1) { // PING
|
||||||
|
rv = "PING";
|
||||||
|
// If we aren't rewriting the PING don't rewrite the PONG
|
||||||
|
// expectedPong.append("PONG 127.0.0.1");
|
||||||
|
} else if (field.length == 2) { // PING nonce
|
||||||
|
rv = "PING " + field[1];
|
||||||
|
// If we aren't rewriting the PING don't rewrite the PONG
|
||||||
|
// expectedPong.append("PONG ").append(field[1]);
|
||||||
|
} else if (field.length == 3) { // PING nonce serverLocation
|
||||||
|
rv = "PING " + field[1];
|
||||||
|
expectedPong.append("PONG ").append(field[2]).append(" :").append(field[1]); // PONG serverLocation nonce
|
||||||
|
} else {
|
||||||
|
//if (_log.shouldLog(Log.ERROR))
|
||||||
|
// _log.error("IRC client sent a PING we don't understand, filtering it (\"" + s + "\")");
|
||||||
|
rv = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if (_log.shouldLog(Log.WARN))
|
||||||
|
// _log.warn("sending ping [" + rv + "], waiting for [" + expectedPong + "] orig was [" + s + "]");
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
if ("PONG".equals(command))
|
||||||
|
return "PONG 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
|
||||||
|
|
||||||
|
// Allow all allowedCommands
|
||||||
|
for(int i=0;i<allowedCommands.length;i++)
|
||||||
|
{
|
||||||
|
if(allowedCommands[i].equals(command))
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mIRC sends "NOTICE user :DCC Send file (IP)"
|
||||||
|
// in addition to the CTCP version
|
||||||
|
if("NOTICE".equals(command))
|
||||||
|
{
|
||||||
|
String msg = field[2];
|
||||||
|
if(msg.startsWith(":DCC "))
|
||||||
|
return filterDCCOut(field[0] + ' ' + field[1] + " :DCC ", msg.substring(5), helper);
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow PRIVMSG, but block CTCP (except ACTION).
|
||||||
|
if("PRIVMSG".equals(command) || "NOTICE".equals(command))
|
||||||
|
{
|
||||||
|
String msg;
|
||||||
|
msg = field[2];
|
||||||
|
|
||||||
|
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
|
||||||
|
{
|
||||||
|
// CTCP
|
||||||
|
|
||||||
|
// don't even try to parse multiple CTCP in the same message
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < msg.length(); i++) {
|
||||||
|
if (msg.charAt(i) == 0x01)
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (count != 2)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
msg=msg.substring(2);
|
||||||
|
if(msg.startsWith("ACTION ")) {
|
||||||
|
// /me says hello
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
if (msg.startsWith("DCC "))
|
||||||
|
return filterDCCOut(field[0] + ' ' + field[1] + " :\001DCC ", msg.substring(4), helper);
|
||||||
|
if (ALLOW_ALL_CTCP_OUT)
|
||||||
|
return s;
|
||||||
|
return null; // Block all other ctcp
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if("USER".equals(command)) {
|
||||||
|
int idx = field[2].lastIndexOf(":");
|
||||||
|
if(idx<0)
|
||||||
|
return "USER user hostname localhost :realname";
|
||||||
|
String realname = field[2].substring(idx+1);
|
||||||
|
String ret = "USER "+field[1]+" hostname localhost :"+realname;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("PART".equals(command)) {
|
||||||
|
// hide client message
|
||||||
|
return "PART " + field[1] + " :leaving";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("QUIT".equals(command)) {
|
||||||
|
return "QUIT :leaving";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block the rest
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param pfx the message through the "DCC " part
|
||||||
|
* @param msg the message after the "DCC " part
|
||||||
|
* @param helper may be null
|
||||||
|
* @return the sanitized message or null to block
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
private static String filterDCCIn(String pfx, String msg, DCCHelper helper) {
|
||||||
|
// strip trailing ctcp (other one is in pfx)
|
||||||
|
int ctcp = msg.indexOf(0x01);
|
||||||
|
if (ctcp > 0)
|
||||||
|
msg = msg.substring(0, ctcp);
|
||||||
|
String[] args = msg.split(" ", 5);
|
||||||
|
if (args.length <= 0)
|
||||||
|
return null;
|
||||||
|
String type = args[0];
|
||||||
|
// no IP in these but port needs to be fixed still
|
||||||
|
//if (type == "RESUME" || type == "ACCEPT")
|
||||||
|
// return msg;
|
||||||
|
if (!(type.equals("CHAT") || type.equals("SEND"))) {
|
||||||
|
if (ALLOW_ALL_DCC_IN) {
|
||||||
|
if (ctcp > 0)
|
||||||
|
return pfx + msg + (char) 0x01;
|
||||||
|
return pfx + msg;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (helper == null || !helper.isEnabled())
|
||||||
|
return null;
|
||||||
|
if (args.length < 4)
|
||||||
|
return null;
|
||||||
|
String arg = args[1];
|
||||||
|
String b32 = args[2];
|
||||||
|
int cPort;
|
||||||
|
try {
|
||||||
|
String cp = args[3];
|
||||||
|
cPort = Integer.parseInt(cp);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int port = helper.newIncoming(b32, cPort, type);
|
||||||
|
if (port < 0)
|
||||||
|
return null;
|
||||||
|
StringBuilder buf = new StringBuilder(256);
|
||||||
|
// fixme what is our address?
|
||||||
|
byte[] myIP = { 127, 0, 0, 1 };
|
||||||
|
buf.append(pfx)
|
||||||
|
.append(type).append(' ').append(arg).append(' ')
|
||||||
|
.append(DataHelper.fromLong(myIP, 0, myIP.length)).append(' ')
|
||||||
|
.append(port);
|
||||||
|
if (args.length > 4)
|
||||||
|
buf.append(' ').append(args[4]);
|
||||||
|
if (pfx.indexOf(0x01) >= 0)
|
||||||
|
buf.append((char) 0x01);
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param pfx the message through the "DCC " part
|
||||||
|
* @param msg the message after the "DCC " part
|
||||||
|
* @param helper may be null
|
||||||
|
* @return the sanitized message or null to block
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
private static String filterDCCOut(String pfx, String msg, DCCHelper helper) {
|
||||||
|
// strip trailing ctcp (other one is in pfx)
|
||||||
|
int ctcp = msg.indexOf(0x01);
|
||||||
|
if (ctcp > 0)
|
||||||
|
msg = msg.substring(0, ctcp);
|
||||||
|
String[] args = msg.split(" ", 5);
|
||||||
|
if (args.length <= 0)
|
||||||
|
return null;
|
||||||
|
String type = args[0];
|
||||||
|
// no IP in these but port needs to be fixed still
|
||||||
|
//if (type == "RESUME" || type == "ACCEPT")
|
||||||
|
// return msg;
|
||||||
|
if (!(type.equals("CHAT") || type.equals("SEND"))) {
|
||||||
|
if (ALLOW_ALL_DCC_OUT) {
|
||||||
|
if (ctcp > 0)
|
||||||
|
return pfx + msg + (char) 0x01;
|
||||||
|
return pfx + msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (helper == null || !helper.isEnabled())
|
||||||
|
return null;
|
||||||
|
if (args.length < 4)
|
||||||
|
return null;
|
||||||
|
String arg = args[1];
|
||||||
|
byte[] ip;
|
||||||
|
try {
|
||||||
|
String ips = args[2];
|
||||||
|
long ipl = Long.parseLong(ips);
|
||||||
|
if (ipl < 0x01000000) {
|
||||||
|
// "reverse/firewall DCC"
|
||||||
|
// http://en.wikipedia.org/wiki/Direct_Client-to-Client
|
||||||
|
// xchat sends an IP of 199 and a port of 0
|
||||||
|
System.err.println("Reverse / Firewall DCC not supported IP = 0x" + Long.toHexString(ipl));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ip = DataHelper.toLong(4, ipl);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int cPort;
|
||||||
|
try {
|
||||||
|
String cp = args[3];
|
||||||
|
cPort = Integer.parseInt(cp);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (cPort <= 0) {
|
||||||
|
// "reverse/firewall DCC"
|
||||||
|
// http://en.wikipedia.org/wiki/Direct_Client-to-Client
|
||||||
|
System.err.println("Reverse / Firewall DCC not supported");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int port = helper.newOutgoing(ip, cPort, type);
|
||||||
|
if (port < 0)
|
||||||
|
return null;
|
||||||
|
StringBuilder buf = new StringBuilder(256);
|
||||||
|
buf.append(pfx)
|
||||||
|
.append(type).append(' ').append(arg).append(' ')
|
||||||
|
.append(helper.getB32Hostname()).append(' ')
|
||||||
|
.append(port);
|
||||||
|
if (args.length > 4)
|
||||||
|
buf.append(' ').append(args[4]);
|
||||||
|
if (pfx.indexOf(0x01) >= 0)
|
||||||
|
buf.append((char) 0x01);
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,101 @@
|
|||||||
|
package net.i2p.i2ptunnel.irc;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread to do inbound filtering.
|
||||||
|
* Moved from I2PTunnelIRCClient.java
|
||||||
|
*
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
public class IrcInboundFilter implements Runnable {
|
||||||
|
|
||||||
|
private final Socket local;
|
||||||
|
private final I2PSocket remote;
|
||||||
|
private final StringBuffer expectedPong;
|
||||||
|
private final Log _log;
|
||||||
|
private final DCCHelper _dccHelper;
|
||||||
|
|
||||||
|
public IrcInboundFilter(Socket lcl, I2PSocket rem, StringBuffer pong, Log log) {
|
||||||
|
this(lcl, rem, pong, log, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param helper may be null
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
public IrcInboundFilter(Socket lcl, I2PSocket rem, StringBuffer pong, Log log, DCCHelper helper) {
|
||||||
|
local = lcl;
|
||||||
|
remote = rem;
|
||||||
|
expectedPong = pong;
|
||||||
|
_log = log;
|
||||||
|
_dccHelper = helper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
// Todo: Don't use BufferedReader - IRC spec limits line length to 512 but...
|
||||||
|
BufferedReader in;
|
||||||
|
OutputStream output;
|
||||||
|
try {
|
||||||
|
in = new BufferedReader(new InputStreamReader(remote.getInputStream(), "ISO-8859-1"));
|
||||||
|
output=local.getOutputStream();
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (_log.shouldLog(Log.ERROR))
|
||||||
|
_log.error("IrcInboundFilter: no streams",e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("IrcInboundFilter: Running.");
|
||||||
|
try {
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
String inmsg = in.readLine();
|
||||||
|
if(inmsg==null)
|
||||||
|
break;
|
||||||
|
if(inmsg.endsWith("\r"))
|
||||||
|
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("in: [" + inmsg + "]");
|
||||||
|
String outmsg = IRCFilter.inboundFilter(inmsg, expectedPong, _dccHelper);
|
||||||
|
if(outmsg!=null)
|
||||||
|
{
|
||||||
|
if(!inmsg.equals(outmsg)) {
|
||||||
|
if (_log.shouldLog(Log.WARN)) {
|
||||||
|
_log.warn("inbound FILTERED: "+outmsg);
|
||||||
|
_log.warn(" - inbound was: "+inmsg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_log.shouldLog(Log.INFO))
|
||||||
|
_log.info("inbound: "+outmsg);
|
||||||
|
}
|
||||||
|
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
|
||||||
|
output.write(outmsg.getBytes("ISO-8859-1"));
|
||||||
|
// probably doesn't do much but can't hurt
|
||||||
|
output.flush();
|
||||||
|
} else {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("inbound BLOCKED: "+inmsg);
|
||||||
|
}
|
||||||
|
} catch (IOException e1) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("IrcInboundFilter: disconnected",e1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (RuntimeException re) {
|
||||||
|
_log.error("Error filtering inbound data", re);
|
||||||
|
} finally {
|
||||||
|
try { local.close(); } catch (IOException e) {}
|
||||||
|
}
|
||||||
|
if(_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("IrcInboundFilter: Done.");
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,101 @@
|
|||||||
|
package net.i2p.i2ptunnel.irc;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread to do inbound filtering.
|
||||||
|
* Moved from I2PTunnelIRCClient.java
|
||||||
|
*
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
public class IrcOutboundFilter implements Runnable {
|
||||||
|
|
||||||
|
private final Socket local;
|
||||||
|
private final I2PSocket remote;
|
||||||
|
private final StringBuffer expectedPong;
|
||||||
|
private final Log _log;
|
||||||
|
private final DCCHelper _dccHelper;
|
||||||
|
|
||||||
|
public IrcOutboundFilter(Socket lcl, I2PSocket rem, StringBuffer pong, Log log) {
|
||||||
|
this(lcl, rem, pong, log, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param helper may be null
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
public IrcOutboundFilter(Socket lcl, I2PSocket rem, StringBuffer pong, Log log, DCCHelper helper) {
|
||||||
|
local = lcl;
|
||||||
|
remote = rem;
|
||||||
|
expectedPong = pong;
|
||||||
|
_log = log;
|
||||||
|
_dccHelper = helper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
// Todo: Don't use BufferedReader - IRC spec limits line length to 512 but...
|
||||||
|
BufferedReader in;
|
||||||
|
OutputStream output;
|
||||||
|
try {
|
||||||
|
in = new BufferedReader(new InputStreamReader(local.getInputStream(), "ISO-8859-1"));
|
||||||
|
output=remote.getOutputStream();
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (_log.shouldLog(Log.ERROR))
|
||||||
|
_log.error("IrcOutboundFilter: no streams",e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("IrcOutboundFilter: Running.");
|
||||||
|
try {
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
String inmsg = in.readLine();
|
||||||
|
if(inmsg==null)
|
||||||
|
break;
|
||||||
|
if(inmsg.endsWith("\r"))
|
||||||
|
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("out: [" + inmsg + "]");
|
||||||
|
String outmsg = IRCFilter.outboundFilter(inmsg, expectedPong, _dccHelper);
|
||||||
|
if(outmsg!=null)
|
||||||
|
{
|
||||||
|
if(!inmsg.equals(outmsg)) {
|
||||||
|
if (_log.shouldLog(Log.WARN)) {
|
||||||
|
_log.warn("outbound FILTERED: "+outmsg);
|
||||||
|
_log.warn(" - outbound was: "+inmsg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_log.shouldLog(Log.INFO))
|
||||||
|
_log.info("outbound: "+outmsg);
|
||||||
|
}
|
||||||
|
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
|
||||||
|
output.write(outmsg.getBytes("ISO-8859-1"));
|
||||||
|
// save 250 ms in streaming
|
||||||
|
output.flush();
|
||||||
|
} else {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("outbound BLOCKED: "+"\""+inmsg+"\"");
|
||||||
|
}
|
||||||
|
} catch (IOException e1) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("IrcOutboundFilter: disconnected",e1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (RuntimeException re) {
|
||||||
|
_log.error("Error filtering outbound data", re);
|
||||||
|
} finally {
|
||||||
|
try { remote.close(); } catch (IOException e) {}
|
||||||
|
}
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("IrcOutboundFilter: Done.");
|
||||||
|
}
|
||||||
|
}
|
@@ -11,7 +11,8 @@ import java.net.Socket;
|
|||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.client.streaming.I2PSocket;
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
import net.i2p.i2ptunnel.I2PTunnel;
|
import net.i2p.i2ptunnel.I2PTunnel;
|
||||||
import net.i2p.i2ptunnel.I2PTunnelIRCClient;
|
import net.i2p.i2ptunnel.irc.IrcInboundFilter;
|
||||||
|
import net.i2p.i2ptunnel.irc.IrcOutboundFilter;
|
||||||
import net.i2p.i2ptunnel.Logging;
|
import net.i2p.i2ptunnel.Logging;
|
||||||
import net.i2p.util.EventDispatcher;
|
import net.i2p.util.EventDispatcher;
|
||||||
import net.i2p.util.I2PAppThread;
|
import net.i2p.util.I2PAppThread;
|
||||||
@@ -50,10 +51,10 @@ public class I2PSOCKSIRCTunnel extends I2PSOCKSTunnel {
|
|||||||
Socket clientSock = serv.getClientSocket();
|
Socket clientSock = serv.getClientSocket();
|
||||||
I2PSocket destSock = serv.getDestinationI2PSocket(this);
|
I2PSocket destSock = serv.getDestinationI2PSocket(this);
|
||||||
StringBuffer expectedPong = new StringBuffer();
|
StringBuffer expectedPong = new StringBuffer();
|
||||||
Thread in = new I2PAppThread(new I2PTunnelIRCClient.IrcInboundFilter(clientSock, destSock, expectedPong, _log),
|
Thread in = new I2PAppThread(new IrcInboundFilter(clientSock, destSock, expectedPong, _log),
|
||||||
"SOCKS IRC Client " + (++__clientId) + " in", true);
|
"SOCKS IRC Client " + (++__clientId) + " in", true);
|
||||||
in.start();
|
in.start();
|
||||||
Thread out = new I2PAppThread(new I2PTunnelIRCClient.IrcOutboundFilter(clientSock, destSock, expectedPong, _log),
|
Thread out = new I2PAppThread(new IrcOutboundFilter(clientSock, destSock, expectedPong, _log),
|
||||||
"SOCKS IRC Client " + __clientId + " out", true);
|
"SOCKS IRC Client " + __clientId + " out", true);
|
||||||
out.start();
|
out.start();
|
||||||
} catch (SOCKSException e) {
|
} catch (SOCKSException e) {
|
||||||
|
Reference in New Issue
Block a user