propagate from branch 'i2p.i2p' (head 793ca7c46f5d8b51c5880fc538dea7874e62f63b)

to branch 'i2p.i2p.zzz.test' (head d39f17fe601b6ae514111b07092de820668015d7)
This commit is contained in:
zzz
2011-08-20 20:23:27 +00:00
49 changed files with 2194 additions and 719 deletions

View File

@@ -49,6 +49,7 @@ import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import net.i2p.I2PAppContext;
@@ -70,16 +71,18 @@ import net.i2p.util.EventDispatcherImpl;
import net.i2p.util.Log;
/**
* An I2PTunnel tracks one or more I2PTunnelTasks and one or more I2PSessions.
* Usually one of each.
*
* Todo: Most events are not listened to elsewhere, so error propagation is poor
*/
public class I2PTunnel implements Logging, EventDispatcher {
public class I2PTunnel extends EventDispatcherImpl implements Logging {
private final Log _log;
private final EventDispatcherImpl _event;
private final I2PAppContext _context;
private static long __tunnelId = 0;
private final long _tunnelId;
private final Properties _clientOptions;
private final List<I2PSession> _sessions;
private final Set<I2PSession> _sessions;
public static final int PACKET_DELAY = 100;
@@ -96,7 +99,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
private static final String nocli_args[] = { "-nocli", "-die"};
private final List tasks = new ArrayList();
private final List<I2PTunnelTask> tasks = new ArrayList();
private int next_task_id = 1;
private final Set listeners = new CopyOnWriteArraySet();
@@ -114,14 +117,14 @@ public class I2PTunnel implements Logging, EventDispatcher {
}
public I2PTunnel(String[] args, ConnectionEventListener lsnr) {
super();
_context = I2PAppContext.getGlobalContext(); // new I2PAppContext();
_tunnelId = ++__tunnelId;
_log = _context.logManager().getLog(I2PTunnel.class);
_event = new EventDispatcherImpl();
// as of 0.8.4, include context properties
Properties p = _context.getProperties();
_clientOptions = p;
_sessions = new ArrayList(1);
_sessions = new CopyOnWriteArraySet();
addConnectionEventListener(lsnr);
boolean gui = true;
@@ -193,22 +196,17 @@ public class I2PTunnel implements Logging, EventDispatcher {
/** @return non-null */
List<I2PSession> getSessions() {
synchronized (_sessions) {
return new ArrayList(_sessions);
}
}
void addSession(I2PSession session) {
if (session == null) return;
synchronized (_sessions) {
if (!_sessions.contains(session))
_sessions.add(session);
}
_sessions.add(session);
}
void removeSession(I2PSession session) {
if (session == null) return;
synchronized (_sessions) {
_sessions.remove(session);
}
_sessions.remove(session);
}
public Properties getClientOptions() { return _clientOptions; }
@@ -218,9 +216,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
if (tsk.isOpen()) {
tsk.setId(next_task_id);
next_task_id++;
synchronized (tasks) {
tasks.add(tsk);
}
tasks.add(tsk);
}
}
@@ -1261,10 +1257,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
*/
public void runQuit(Logging l) {
purgetasks(l);
synchronized (tasks) {
if (tasks.isEmpty()) {
System.exit(0);
}
if (tasks.isEmpty()) {
System.exit(0);
}
l.log("There are running tasks. Try 'list'.");
notifyEvent("quitResult", "error");
@@ -1280,11 +1274,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
*/
public void runList(Logging l) {
purgetasks(l);
synchronized (tasks) {
for (int i = 0; i < tasks.size(); i++) {
I2PTunnelTask t = (I2PTunnelTask) tasks.get(i);
l.log("[" + t.getId() + "] " + t.toString());
}
for (I2PTunnelTask t : tasks) {
l.log("[" + t.getId() + "] " + t.toString());
}
notifyEvent("listDone", "done");
}
@@ -1313,14 +1304,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
argindex++;
}
if (args[argindex].equalsIgnoreCase("all")) {
List curTasks = null;
synchronized (tasks) {
curTasks = new LinkedList(tasks);
}
boolean error = false;
for (int i = 0; i < curTasks.size(); i++) {
I2PTunnelTask t = (I2PTunnelTask) curTasks.get(i);
for (I2PTunnelTask t : tasks) {
if (!closetask(t, forced, l)) {
notifyEvent("closeResult", "error");
error = true;
@@ -1442,9 +1427,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
boolean closed = false;
_log.debug(getPrefix() + "closetask(): looking for task " + num);
synchronized (tasks) {
for (Iterator it = tasks.iterator(); it.hasNext();) {
I2PTunnelTask t = (I2PTunnelTask) it.next();
for (I2PTunnelTask t : tasks) {
int id = t.getId();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix() + "closetask(): parsing task " + id + " (" + t.toString() + ")");
@@ -1454,7 +1437,6 @@ public class I2PTunnel implements Logging, EventDispatcher {
} else if (id > num) {
break;
}
}
}
return closed;
}
@@ -1482,15 +1464,14 @@ public class I2PTunnel implements Logging, EventDispatcher {
*
*/
private void purgetasks(Logging l) {
synchronized (tasks) {
for (Iterator it = tasks.iterator(); it.hasNext();) {
I2PTunnelTask t = (I2PTunnelTask) it.next();
List<I2PTunnelTask> removed = new ArrayList();
for (I2PTunnelTask t : tasks) {
if (!t.isOpen()) {
_log.debug(getPrefix() + "Purging inactive tunnel: [" + t.getId() + "] " + t.toString());
it.remove();
removed.add(t);
}
}
}
tasks.removeAll(removed);
}
/**
@@ -1657,41 +1638,4 @@ public class I2PTunnel implements Logging, EventDispatcher {
public interface ConnectionEventListener {
public void routerDisconnected();
}
/* Required by the EventDispatcher interface */
public EventDispatcher getEventDispatcher() {
return _event;
}
public void attachEventDispatcher(EventDispatcher e) {
_event.attachEventDispatcher(e.getEventDispatcher());
}
public void detachEventDispatcher(EventDispatcher e) {
_event.detachEventDispatcher(e.getEventDispatcher());
}
public void notifyEvent(String e, Object a) {
_event.notifyEvent(e, a);
}
public Object getEventValue(String n) {
return _event.getEventValue(n);
}
public Set getEvents() {
return _event.getEvents();
}
public void ignoreEvents() {
_event.ignoreEvents();
}
public void unIgnoreEvents() {
_event.unIgnoreEvents();
}
public Object waitEventValue(String n) {
return _event.waitEventValue(n);
}
}

View File

@@ -51,7 +51,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
protected long _clientId;
protected final Object sockLock = new Object(); // Guards sockMgr and mySockets
protected I2PSocketManager sockMgr; // should be final and use a factory. LINT
protected List mySockets = new ArrayList();
protected final List<I2PSocket> mySockets = new ArrayList();
protected boolean _ownDest;
protected Destination dest = null;
@@ -59,7 +59,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private boolean listenerReady = false;
private ServerSocket ss;
protected ServerSocket ss;
private final Object startLock = new Object();
private boolean startRunning = false;
@@ -196,7 +196,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
// no need to load the netDb with leaseSets for destinations that will never
// be looked up
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
boolean dccEnabled = (this instanceof I2PTunnelIRCClient) &&
Boolean.valueOf(tunnel.getClientOptions().getProperty(I2PTunnelIRCClient.PROP_DCC)).booleanValue();
if (!dccEnabled)
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
boolean openNow = !Boolean.valueOf(tunnel.getClientOptions().getProperty("i2cp.delayOpen")).booleanValue();
if (openNow) {
@@ -683,11 +686,11 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
synchronized (sockLock) {
if (sockMgr != null) {
mySockets.retainAll(sockMgr.listSockets());
if (!forced && mySockets.size() != 0) {
l.log("There are still active connections!");
if ((!forced) && (!mySockets.isEmpty())) {
l.log("Not closing, there are still active connections!");
_log.debug("can't close: there are still active connections!");
for (Iterator it = mySockets.iterator(); it.hasNext();) {
l.log("->" + it.next());
for (I2PSocket s : mySockets) {
l.log(" -> " + s.toString());
}
return false;
}
@@ -703,7 +706,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
try {
if (ss != null) ss.close();
} catch (IOException ex) {
ex.printStackTrace();
if (_log.shouldLog(Log.WARN))
_log.warn("error closing", ex);
return false;
}
//l.log("Client closed.");

View File

@@ -25,7 +25,8 @@ import net.i2p.util.Log;
*/
public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner {
private Log _log;
public I2PTunnelHTTPClientRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
public I2PTunnelHTTPClientRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData,
List<I2PSocket> sockList, Runnable onTimeout) {
super(s, i2ps, slock, initialI2PData, sockList, onTimeout);
_log = I2PAppContext.getGlobalContext().logManager().getLog(I2PTunnelHTTPClientRunner.class);
}

View File

@@ -10,7 +10,13 @@ import java.util.List;
import java.util.StringTokenizer;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.Base32;
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.I2PAppThread;
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?
*/
public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable {
public class I2PTunnelIRCClient extends I2PTunnelClientBase {
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
@@ -27,6 +33,14 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
protected List<Destination> dests;
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
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
@@ -75,23 +89,28 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
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();
notifyEvent("openIRCClientResult", "ok");
}
protected void clientConnectionRun(Socket s) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("got a connection.");
if (_log.shouldLog(Log.INFO))
_log.info("New connection local addr is: " + s.getLocalAddress() +
" from: " + s.getInetAddress());
Destination clientDest = pickDestination();
I2PSocket i2ps = null;
try {
i2ps = createI2PSocket(clientDest);
i2ps.setReadTimeout(readTimeout);
StringBuffer expectedPong = new StringBuffer();
Thread in = new I2PAppThread(new IrcInboundFilter(s,i2ps, expectedPong, _log), "IRC Client " + __clientId + " in", true);
DCCHelper dcc = _dccEnabled ? new DCC(s.getLocalAddress().getAddress()) : null;
Thread in = new I2PAppThread(new IrcInboundFilter(s,i2ps, expectedPong, _log, dcc), "IRC Client " + __clientId + " in", true);
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, dcc), "IRC Client " + __clientId + " out", true);
out.start();
} catch (Exception ex) {
if (_log.shouldLog(Log.ERROR))
@@ -120,388 +139,112 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
return dests.get(index);
}
/*************************************************************************
*
*/
public static class IrcInboundFilter implements Runnable {
private final Socket local;
private final I2PSocket remote;
private final StringBuffer expectedPong;
private final Log _log;
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;
@Override
public boolean close(boolean forced) {
synchronized(this) {
if (_DCCServer != null) {
_DCCServer.close(forced);
_DCCServer = null;
}
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.");
if (_DCCClientManager != null) {
_DCCClientManager.close(forced);
_DCCClientManager = null;
}
}
/*************************************************************************
*
*/
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;
// 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;
return super.close(forced);
}
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();
//
// Start of the DCCHelper interface
//
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.
private class DCC implements DCCHelper {
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
private final byte[] _localAddr;
// 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 null;
// 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
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;
/**
* @param local Our IP address, from the IRC client's perspective
*/
public DCC(byte[] local) {
if (local.length == 4)
_localAddr = local;
else
_localAddr = new byte[] {127, 0, 0, 1};
}
public boolean isEnabled() {
return _dccEnabled;
}
public String getB32Hostname() {
return Base32.encode(sockMgr.getSession().getMyDestination().calculateHash().getData()) + ".b32.i2p";
}
public byte[] getLocalAddress() {
return _localAddr;
}
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, I2PTunnelIRCClient.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;
}
public int newIncoming(String b32, int port, String type) {
DCCClientManager tracker;
synchronized(this) {
if (_DCCClientManager == null) {
if (_log.shouldLog(Log.INFO))
_log.info("Starting DCC Client");
_DCCClientManager = new DCCClientManager(sockMgr, l, I2PTunnelIRCClient.this, getTunnel());
}
tracker = _DCCClientManager;
}
// The tracker starts our client
int rv = tracker.newIncoming(b32, port, type);
if (_log.shouldLog(Log.INFO))
_log.info("New incoming " + type + ' ' + b32 + ' ' + port + " returns " + rv);
return rv;
}
public int resumeOutgoing(int port) {
DCCClientManager tracker = _DCCClientManager;
if (tracker != null)
return tracker.resumeOutgoing(port);
return -1;
}
public int resumeIncoming(int port) {
I2PTunnelDCCServer server = _DCCServer;
if (server != null)
return server.resumeIncoming(port);
return -1;
}
public int acceptOutgoing(int port) {
I2PTunnelDCCServer server = _DCCServer;
if (server != null)
return server.acceptOutgoing(port);
return -1;
}
public int acceptIncoming(int port) {
DCCClientManager tracker = _DCCClientManager;
if (tracker != null)
return tracker.acceptIncoming(port);
return -1;
}
}
}

View File

@@ -20,10 +20,10 @@ import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErrorListener {
private final static Log _log = new Log(I2PTunnelRunner.class);
private final Log _log = new Log(I2PTunnelRunner.class);
private static volatile long __runnerId;
private long _runnerId;
private final long _runnerId;
/**
* max bytes streamed in a packet - smaller ones might be filled
* up to this size. Larger ones are not split (at least not on
@@ -34,35 +34,51 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
static final int NETWORK_BUFFER_SIZE = MAX_PACKET_SIZE;
private Socket s;
private I2PSocket i2ps;
final Object slock, finishLock = new Object();
private final Socket s;
private final I2PSocket i2ps;
private final Object slock, finishLock = new Object();
boolean finished = false;
HashMap ostreams, sockets;
byte[] initialI2PData;
byte[] initialSocketData;
private final byte[] initialI2PData;
private final byte[] initialSocketData;
/** when the last data was sent/received (or -1 if never) */
private long lastActivityOn;
/** when the runner started up */
private long startedOn;
private List sockList;
private final long startedOn;
private final List<I2PSocket> sockList;
/** if we die before receiving any data, run this job */
private Runnable onTimeout;
private final Runnable onTimeout;
private long totalSent;
private long totalReceived;
private volatile long __forwarderId;
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList) {
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData,
List<I2PSocket> sockList) {
this(s, i2ps, slock, initialI2PData, null, sockList, null);
}
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList) {
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData,
byte[] initialSocketData, List<I2PSocket> sockList) {
this(s, i2ps, slock, initialI2PData, initialSocketData, sockList, null);
}
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData,
List<I2PSocket> sockList, Runnable onTimeout) {
this(s, i2ps, slock, initialI2PData, null, sockList, onTimeout);
}
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList, Runnable onTimeout) {
/**
* Starts itself
*
* @param slock the socket lock, non-null
* @param initialI2PData may be null
* @param initialSocketData may be null
* @param sockList may be null. Caller must add i2ps to the list! It will be removed here on completion.
* Will synchronize on slock when removing.
* @param onTImeout may be null
*/
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData,
byte[] initialSocketData, List<I2PSocket> sockList, Runnable onTimeout) {
this.sockList = sockList;
this.s = s;
this.i2ps = i2ps;
@@ -84,6 +100,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
* have we closed at least one (if not both) of the streams
* [aka we're done running the streams]?
*
* @deprecated unused
*/
public boolean isFinished() {
return finished;
@@ -93,7 +110,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
* When was the last data for this runner sent or received?
*
* @return date (ms since the epoch), or -1 if no data has been transferred yet
*
* @deprecated unused
*/
public long getLastActivityOn() {
return lastActivityOn;
@@ -237,11 +254,11 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
private class StreamForwarder extends I2PAppThread {
InputStream in;
OutputStream out;
String direction;
private boolean _toI2P;
private ByteCache _cache;
private final InputStream in;
private final OutputStream out;
private final String direction;
private final boolean _toI2P;
private final ByteCache _cache;
private StreamForwarder(InputStream in, OutputStream out, boolean toI2P) {
this.in = in;

View File

@@ -47,7 +47,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
protected int remotePort;
private boolean _usePool;
private Logging l;
protected Logging l;
private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
/** default timeout to 3 minutes - override if desired */
@@ -69,10 +69,13 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
protected boolean bidir = false;
private ThreadPoolExecutor _executor;
/** unused? port should always be specified */
private int DEFAULT_LOCALPORT = 4488;
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
* 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
* 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
* badly that we cant create a socketManager
*/
@@ -114,10 +123,28 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
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 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
* badly that we cant create a socketManager
*/

View File

@@ -13,9 +13,7 @@ import net.i2p.util.EventDispatcherImpl;
* Either a Server or a Client.
*/
public abstract class I2PTunnelTask implements EventDispatcher {
private final EventDispatcherImpl _event = new EventDispatcherImpl();
public abstract class I2PTunnelTask extends EventDispatcherImpl {
private int id;
private String name;
@@ -77,41 +75,4 @@ public abstract class I2PTunnelTask implements EventDispatcher {
public String toString() {
return name;
}
/* Required by the EventDispatcher interface */
public EventDispatcher getEventDispatcher() {
return _event;
}
public void attachEventDispatcher(EventDispatcher e) {
_event.attachEventDispatcher(e.getEventDispatcher());
}
public void detachEventDispatcher(EventDispatcher e) {
_event.detachEventDispatcher(e.getEventDispatcher());
}
public void notifyEvent(String e, Object a) {
_event.notifyEvent(e, a);
}
public Object getEventValue(String n) {
return _event.getEventValue(n);
}
public Set getEvents() {
return _event.getEvents();
}
public void ignoreEvents() {
_event.ignoreEvents();
}
public void unIgnoreEvents() {
_event.unIgnoreEvents();
}
public Object waitEventValue(String n) {
return _event.waitEventValue(n);
}
}
}

View File

@@ -22,16 +22,19 @@ import net.i2p.util.Log;
import net.i2p.util.SecureFileOutputStream;
/**
* Coordinate the runtime operation and configuration of a tunnel.
* Coordinate the runtime operation and configuration of a single I2PTunnel.
* An I2PTunnel tracks one or more I2PTunnelTasks and one or more I2PSessions.
* Usually one of each.
*
* These objects are bundled together under a TunnelControllerGroup where the
* entire group is stored / loaded from a single config file.
*
*/
public class TunnelController implements Logging {
private Log _log;
private final Log _log;
private Properties _config;
private I2PTunnel _tunnel;
private List<String> _messages;
private final I2PTunnel _tunnel;
private final List<String> _messages;
private List<I2PSession> _sessions;
private boolean _running;
private boolean _starting;

View File

@@ -0,0 +1,236 @@
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.data.Base32;
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>
*
* direct conn
* <---> I2PTunnelDCCServer <--------------->I2PTunnelDCCClient <---->
* originating responding
* chat client chat client
* CHAT ---> I2PTunnelIRCClient --> IRC server --> I2TunnelIRCClient ----->
* SEND ---> I2PTunnelIRCClient --> IRC server --> I2TunnelIRCClient ----->
* RESUME <--- I2PTunnelIRCClient <-- IRC server <-- I2TunnelIRCClient <-----
* ACCEPT ---> I2PTunnelIRCClient --> IRC server --> I2TunnelIRCClient ----->
*
* </pre>
*
* @since 0.8.9
*/
public class DCCClientManager extends EventReceiver {
private final I2PSocketManager sockMgr;
private final EventDispatcher _dispatch;
private final Logging l;
private final I2PTunnel _tunnel;
private final Log _log;
/** key is the DCC client's local port */
private final ConcurrentHashMap<Integer, I2PTunnelDCCClient> _incoming;
/** key is the DCC client's local port */
private final ConcurrentHashMap<Integer, I2PTunnelDCCClient> _active;
/** key is the DCC client's local port */
private final ConcurrentHashMap<Integer, I2PTunnelDCCClient> _complete;
// 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 ACTIVE_EXPIRE = 60*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);
_active = new ConcurrentHashMap(8);
_complete = new ConcurrentHashMap(8);
}
public boolean close(boolean forced) {
for (I2PTunnelDCCClient c : _incoming.values()) {
c.stop();
}
_incoming.clear();
for (I2PTunnelDCCClient c : _active.values()) {
c.stop();
}
_active.clear();
_complete.clear();
return true;
}
/**
* An incoming DCC request
*
* @param b32 remote dcc server b32 address
* @param port remote dcc server I2P port
* @param type ignored
* @return local DCC client tunnel port or -1 on error
*/
public int newIncoming(String b32, int port, String type) {
return newIncoming(b32, port, type, 0);
}
/**
* @param localPort bind to port or 0; if nonzero it will be the rv
*/
private int newIncoming(String b32, int port, String type, int localPort) {
b32 = b32.toLowerCase();
// do some basic verification before starting the client
if (b32.length() != 60 || !b32.endsWith(".b32.i2p"))
return -1;
byte[] dec = Base32.decode(b32.substring(0, 52));
if (dec == null || dec.length != 32)
return -1;
expireInbound();
if (_incoming.size() >= MAX_INCOMING_PENDING ||
_active.size() >= MAX_INCOMING_PENDING) {
_log.error("Too many incoming DCC, max is " + MAX_INCOMING_PENDING +
'/' + MAX_INCOMING_ACTIVE + " pending/active");
return -1;
}
try {
// Transparent tunnel used for all types...
// Do we need to do any filtering for chat?
I2PTunnelDCCClient cTunnel = new I2PTunnelDCCClient(b32, localPort, port, l, sockMgr,
_dispatch, _tunnel, ++_id);
cTunnel.attachEventDispatcher(this);
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), cTunnel);
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;
}
}
/**
* An outgoing RESUME request
*
* @param port local DCC client tunnel port
* @return remote DCC server i2p port or -1 on error
*/
public int resumeOutgoing(int port) {
Integer lport = Integer.valueOf(port);
I2PTunnelDCCClient tun = _complete.get(lport);
if (tun == null) {
tun = _active.get(lport);
if (tun == null)
// shouldn't happen
tun = _incoming.get(lport);
}
if (tun != null) {
tun.stop();
return tun.getLocalPort();
}
return -1;
}
/**
* An incoming ACCEPT response
*
* @param port remote dcc server I2P port
* @return local DCC client tunnel port or -1 on error
*/
public int acceptIncoming(int port) {
// do a reverse lookup
for (I2PTunnelDCCClient tun : _complete.values()) {
if (tun.getRemotePort() == port)
return newIncoming(tun.getDest(), port, "ACCEPT", tun.getLocalPort());
}
for (I2PTunnelDCCClient tun : _active.values()) {
if (tun.getRemotePort() == port)
return newIncoming(tun.getDest(), port, "ACCEPT", tun.getLocalPort());
}
for (I2PTunnelDCCClient tun : _incoming.values()) {
if (tun.getRemotePort() == port) {
// shouldn't happen
tun.stop();
return newIncoming(tun.getDest(), port, "ACCEPT", tun.getLocalPort());
}
}
return -1;
}
/**
* The EventReceiver callback
*/
public void notifyEvent(String eventName, Object args) {
if (eventName.equals(I2PTunnelDCCClient.CONNECT_START_EVENT)) {
try {
I2PTunnelDCCClient client = (I2PTunnelDCCClient) args;
connStarted(client);
} catch (ClassCastException cce) {}
} else if (eventName.equals(I2PTunnelDCCClient.CONNECT_STOP_EVENT)) {
try {
Integer port = (Integer) args;
connStopped(port);
} catch (ClassCastException cce) {}
}
}
private void connStarted(I2PTunnelDCCClient client) {
Integer lport = Integer.valueOf(client.getLocalPort());
I2PTunnelDCCClient c = _incoming.remove(lport);
if (c != null) {
_active.put(lport, client);
if (_log.shouldLog(Log.WARN))
_log.warn("Added client tunnel for port " + lport +
" pending count now: " + _incoming.size() +
" active count now: " + _active.size() +
" complete count now: " + _complete.size());
}
}
private void connStopped(Integer lport) {
I2PTunnelDCCClient tun = _incoming.remove(lport);
if (tun != null)
_complete.put(lport, tun);
tun = _active.remove(lport);
if (tun != null)
_complete.put(lport, tun);
if (_log.shouldLog(Log.WARN))
_log.warn("Removed client tunnel for port " + lport +
" pending count now: " + _incoming.size() +
" active count now: " + _active.size() +
" complete count now: " + _complete.size());
}
private void expireInbound() {
for (Iterator<I2PTunnelDCCClient> iter = _incoming.values().iterator(); iter.hasNext(); ) {
I2PTunnelDCCClient c = iter.next();
if (c.getExpires() < _tunnel.getContext().clock().now()) {
iter.remove();
c.stop();
}
}
// shouldn't need to expire active
for (Iterator<I2PTunnelDCCClient> iter = _complete.values().iterator(); iter.hasNext(); ) {
I2PTunnelDCCClient c = iter.next();
if (c.getExpires() < _tunnel.getContext().clock().now()) {
iter.remove();
c.stop();
}
}
}
}

View File

@@ -0,0 +1,74 @@
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();
/**
* Our IP address (taken from the socket), must be IPv4
*/
public byte[] getLocalAddress();
/**
* An outgoing DCC request
*
* @param ip local irc client IP
* @param port local irc client port
* @param type string
* @return local DCC server i2p port or -1 on error
*/
public int newOutgoing(byte[] ip, int port, String type);
/**
* An incoming DCC request
*
* @param b32 remote dcc server b32 address
* @param port remote dcc server I2P port
* @param type string
* @return local DCC client tunnel port or -1 on error
*/
public int newIncoming(String b32, int port, String type);
/**
* An outgoing RESUME request
*
* @param port local DCC client tunnel port
* @return remote DCC server i2p port or -1 on error
*/
public int resumeOutgoing(int port);
/**
* An incoming RESUME request
*
* @param port local dcc server I2P port
* @return local IRC client DCC port or -1 on error
*/
public int resumeIncoming(int port);
/**
* An outgoing ACCEPT response
*
* @param port local irc client DCC port
* @return local DCC server i2p port or -1 on error
*/
public int acceptOutgoing(int port);
/**
* An incoming ACCEPT response
*
* @param port remote dcc server I2P port
* @return local DCC client tunnel port or -1 on error
*/
public int acceptIncoming(int port);
}

View File

@@ -0,0 +1,76 @@
package net.i2p.i2ptunnel.irc;
/*
* free (adj.): unencumbered; not under the control of others Written
* by human & jrandom in 2004 and released into the public domain with
* no warranty of any kind, either expressed or implied. It probably
* won't make your computer catch on fire, or eat your children, but
* it might. Use at your own risk.
*
*/
import java.util.Set;
import net.i2p.util.EventDispatcher;
/**
* An implementation of the EventDispatcher interface for
* receiving events via in-line notifyEvent() only.
* Does not support chaining to additional dispatchers.
* Does not support waitEventValue().
* Does not support ignoring.
*
* @since 0.8.9
*/
public abstract class EventReceiver implements EventDispatcher {
public EventDispatcher getEventDispatcher() {
return this;
}
/**
* @throws UnsupportedOperationException always
*/
public void attachEventDispatcher(EventDispatcher ev) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
public void detachEventDispatcher(EventDispatcher ev) {
throw new UnsupportedOperationException();
}
public abstract void notifyEvent(String eventName, Object args);
/**
* @throws UnsupportedOperationException always
*/
public Object getEventValue(String name) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
public Set<String> getEvents() {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
public void ignoreEvents() {
throw new UnsupportedOperationException();
}
public void unIgnoreEvents() {}
/**
* @throws UnsupportedOperationException always
*/
public Object waitEventValue(String name) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,132 @@
/* 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;
private long _expires;
private static final long INBOUND_EXPIRE = 30*60*1000;
private static final long INBOUND_STOP_EXPIRE = 30*60*1000;
public static final String CONNECT_START_EVENT = "connectionStarted";
public static final String CONNECT_STOP_EVENT = "connectionStopped";
/**
* @param dest the target, presumably b32
* @param localPort if 0, use any port, get actual port selected with getLocalPort()
* @throws IllegalArgumentException if the I2PTunnel does not contain
* valid config to contact the router
*/
public I2PTunnelDCCClient(String dest, int localPort, int remotePort, Logging l,
I2PSocketManager sktMgr, EventDispatcher notifyThis,
I2PTunnel tunnel, long clientId) throws IllegalArgumentException {
super(localPort, l, sktMgr, tunnel, notifyThis, clientId);
_dest = dest;
_remotePort = remotePort;
_expires = tunnel.getContext().clock().now() + INBOUND_EXPIRE;
setName("DCC send -> " + dest + ':' + remotePort);
startRunning();
}
/**
* Accept one connection only.
*/
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);
stop();
notifyEvent(CONNECT_STOP_EVENT, Integer.valueOf(getLocalPort()));
return;
}
I2PSocketOptions opts = sockMgr.buildOptions();
opts.setPort(_remotePort);
try {
i2ps = createI2PSocket(dest, opts);
new Runner(s, i2ps);
} 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) {}
}
notifyEvent(CONNECT_STOP_EVENT, Integer.valueOf(getLocalPort()));
}
stop();
}
public long getExpires() {
return _expires;
}
public String getDest() {
return _dest;
}
public int getRemotePort() {
return _remotePort;
}
/**
* Stop listening for new sockets.
* We can't call super.close() as it kills all sockets in the sockMgr
*/
public void stop() {
open = false;
try {
ss.close();
} catch (IOException ioe) {}
}
/**
* Just so we can do the callbacks
*/
private class Runner extends I2PTunnelRunner {
public Runner(Socket s, I2PSocket i2ps) {
// super calls start()
super(s, i2ps, sockLock, null, mySockets);
}
@Override
public void run() {
_expires = getTunnel().getContext().clock().now() + INBOUND_STOP_EXPIRE;
notifyEvent(CONNECT_START_EVENT, I2PTunnelDCCClient.this);
super.run();
_expires = getTunnel().getContext().clock().now() + INBOUND_STOP_EXPIRE;
notifyEvent(CONNECT_STOP_EVENT, Integer.valueOf(getLocalPort()));
}
}
}

View File

@@ -0,0 +1,272 @@
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.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
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>
*
* direct conn
* <---> I2PTunnelDCCServer <--------------->I2PTunnelDCCClient <---->
* originating responding
* chat client chat client
* CHAT ---> I2PTunnelIRCClient --> IRC server --> I2TunnelIRCClient ----->
* SEND ---> I2PTunnelIRCClient --> IRC server --> I2TunnelIRCClient ----->
* RESUME <--- I2PTunnelIRCClient <-- IRC server <-- I2TunnelIRCClient <-----
* ACCEPT ---> I2PTunnelIRCClient --> IRC server --> I2TunnelIRCClient ----->
*
* </pre>
*
* @since 0.8.9
*/
public class I2PTunnelDCCServer extends I2PTunnelServer {
/** key is the server's local I2P port */
private final ConcurrentHashMap<Integer, LocalAddress> _outgoing;
/** key is the server's local I2P port */
private final ConcurrentHashMap<Integer, LocalAddress> _active;
/** key is the server's local I2P port */
private final ConcurrentHashMap<Integer, LocalAddress> _resume;
private final List<I2PSocket> _sockList;
// 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;
private static final long ACTIVE_EXPIRE = 60*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);
_active = new ConcurrentHashMap(8);
_resume = new ConcurrentHashMap(8);
_sockList = new CopyOnWriteArrayList();
}
/**
* 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();
// Port is a one-time-use only
LocalAddress local = _outgoing.remove(Integer.valueOf(myPort));
if (local == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Rejecting 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);
_sockList.add(socket);
new I2PTunnelRunner(s, socket, slock, null, _sockList);
local.socket = socket;
local.expire = getTunnel().getContext().clock().now() + OUTBOUND_EXPIRE;
_active.put(Integer.valueOf(myPort), local);
} 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);
}
}
@Override
public boolean close(boolean forced) {
_outgoing.clear();
_active.clear();
for (I2PSocket s : _sockList) {
try {
s.close();
} catch (IOException ioe) {}
}
_sockList.clear();
return super.close(forced);
}
/**
* 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) {
return newOutgoing(ip, port, type, 0);
}
/**
* @param port local dcc server I2P port or 0 to pick one at random
*/
private int newOutgoing(byte[] ip, int port, String type, int i2pPort) {
expireOutbound();
if (_outgoing.size() >= MAX_OUTGOING_PENDING ||
_active.size() >= MAX_OUTGOING_ACTIVE) {
_log.error("Too many outgoing DCC, max is " + MAX_OUTGOING_PENDING +
'/' + MAX_OUTGOING_ACTIVE + " pending/active");
return -1;
}
InetAddress ia;
try {
ia = InetAddress.getByAddress(ip);
} catch (UnknownHostException uhe) {
return -1;
}
int limit = i2pPort > 0 ? 10 : 1;
LocalAddress client = new LocalAddress(ia, port, getTunnel().getContext().clock().now() + OUTBOUND_EXPIRE);
for (int i = 0; i < limit; i++) {
int iport;
if (i2pPort > 0)
iport = i2pPort;
else
iport = MIN_I2P_PORT + getTunnel().getContext().random().nextInt(1 + MAX_I2P_PORT - MIN_I2P_PORT);
if (_active.containsKey(Integer.valueOf(iport)))
continue;
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;
}
/**
* An incoming RESUME request
*
* @param port local dcc server I2P port
* @return local IRC client DCC port or -1 on error
*/
public int resumeIncoming(int port) {
Integer iport = Integer.valueOf(port);
LocalAddress local = _active.remove(iport);
if (local != null) {
local.expire = getTunnel().getContext().clock().now() + OUTBOUND_EXPIRE;
_resume.put(Integer.valueOf(local.port), local);
return local.port;
}
local = _outgoing.get(iport);
if (local != null) {
// shouldn't happen
local.expire = getTunnel().getContext().clock().now() + OUTBOUND_EXPIRE;
return local.port;
}
return -1;
}
/**
* An outgoing ACCEPT response
*
* @param port local irc client DCC port
* @return local DCC server i2p port or -1 on error
*/
public int acceptOutgoing(int port) {
// do a reverse lookup
for (Iterator<Map.Entry<Integer, LocalAddress>> iter = _resume.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry<Integer, LocalAddress> e = iter.next();
LocalAddress local = e.getValue();
if (local.port == port) {
iter.remove();
return newOutgoing(local.ia.getAddress(), port, "ACCEPT", e.getKey().intValue());
}
}
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();
}
for (Iterator<LocalAddress> iter = _active.values().iterator(); iter.hasNext(); ) {
LocalAddress a = iter.next();
I2PSocket s = a.socket;
if (s != null && s.isClosed())
iter.remove();
}
}
private static class LocalAddress {
public final InetAddress ia;
public final int port;
public long expire;
public I2PSocket socket;
public LocalAddress(InetAddress a, int p, long exp) {
ia = a;
port = p;
expire = exp;
}
}
}

View File

@@ -0,0 +1,483 @@
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);
}
// XDCC looks safe, ip/port happens over regular DCC
// http://en.wikipedia.org/wiki/XDCC
if (msg.toUpperCase().startsWith("XDCC ") && helper != null && helper.isEnabled())
return s;
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);
// XDCC looks safe, ip/port happens over regular DCC
// http://en.wikipedia.org/wiki/XDCC
if (msg.toUpperCase().startsWith("XDCC ") && helper != null && helper.isEnabled())
return s;
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;
}
/**
*<pre>
* DCC CHAT chat xxx.b32.i2p i2p-port -> DCC CHAT chat IP port
* DCC SEND file xxx.b32.i2p i2p-port length -> DCC SEND file IP port length
* DCC RESUME file i2p-port offset -> DCC RESUME file port offset
* DCC ACCEPT file i2p-port offset -> DCC ACCEPT file port offset
* DCC xxx -> null
*</pre>
*
* @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];
boolean haveIP = true;
// no IP in these, replace port only
if (type == "RESUME" || type == "ACCEPT") {
haveIP = false;
} else 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 < 3)
return null;
if (haveIP && args.length < 4)
return null;
String arg = args[1];
int nextArg = 2;
String b32 = null;
if (haveIP)
b32 = args[nextArg++];
int cPort;
try {
String cp = args[nextArg++];
cPort = Integer.parseInt(cp);
} catch (NumberFormatException nfe) {
return null;
}
int port = -1;
if (haveIP) {
port = helper.newIncoming(b32, cPort, type);
} else if (type.equals("ACCEPT")) {
port = helper.acceptIncoming(cPort);
} else if (type.equals("RESUME")) {
port = helper.resumeIncoming(cPort);
}
if (port < 0)
return null;
StringBuilder buf = new StringBuilder(256);
buf.append(pfx)
.append(type).append(' ').append(arg).append(' ');
if (haveIP) {
byte[] myIP = helper.getLocalAddress();
buf.append(DataHelper.fromLong(myIP, 0, myIP.length)).append(' ');
}
buf.append(port);
while (args.length > nextArg) {
buf.append(' ').append(args[nextArg++]);
}
if (pfx.indexOf(0x01) >= 0)
buf.append((char) 0x01);
return buf.toString();
}
/**
*<pre>
* DCC CHAT chat IP port -> DCC CHAT chat xxx.b32.i2p i2p-port
* DCC SEND file IP port length -> DCC SEND file xxx.b32.i2p i2p-port length
* DCC RESUME file port offset -> DCC RESUME file i2p-port offset
* DCC ACCEPT file port offset -> DCC ACCEPT file i2p-port offset
* DCC xxx -> null
*</pre>
*
* @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];
boolean haveIP = true;
// no IP in these, replace port only
if (type == "RESUME" || type == "ACCEPT") {
haveIP = false;
} else 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 < 3)
return null;
if (haveIP && args.length < 4)
return null;
String arg = args[1];
byte[] ip = null;
int nextArg = 2;
if (haveIP) {
try {
String ips = args[nextArg++];
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[nextArg++];
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 = -1;
if (haveIP) {
port = helper.newOutgoing(ip, cPort, type);
} else if (type.equals("ACCEPT")) {
port = helper.acceptOutgoing(cPort);
} else if (type.equals("RESUME")) {
port = helper.resumeOutgoing(cPort);
}
if (port < 0)
return null;
StringBuilder buf = new StringBuilder(256);
buf.append(pfx)
.append(type).append(' ').append(arg).append(' ');
if (haveIP)
buf.append(helper.getB32Hostname()).append(' ');
buf.append(port);
while (args.length > nextArg) {
buf.append(' ').append(args[nextArg++]);
}
if (pfx.indexOf(0x01) >= 0)
buf.append((char) 0x01);
return buf.toString();
}
}

View File

@@ -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.");
}
}

View File

@@ -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.");
}
}

View File

@@ -11,7 +11,8 @@ import java.net.Socket;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocket;
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.util.EventDispatcher;
import net.i2p.util.I2PAppThread;
@@ -50,10 +51,10 @@ public class I2PSOCKSIRCTunnel extends I2PSOCKSTunnel {
Socket clientSock = serv.getClientSocket();
I2PSocket destSock = serv.getDestinationI2PSocket(this);
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);
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);
out.start();
} catch (SOCKSException e) {

View File

@@ -21,6 +21,7 @@ import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
import net.i2p.i2ptunnel.I2PTunnelIRCClient;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.Addresses;
@@ -170,6 +171,11 @@ public class EditBean extends IndexBean {
return getBooleanProperty(tunnel, "i2cp.encryptLeaseSet");
}
/** @since 0.8.9 */
public boolean getDCC(int tunnel) {
return getBooleanProperty(tunnel, I2PTunnelIRCClient.PROP_DCC);
}
public String getEncryptKey(int tunnel) {
return getProperty(tunnel, "i2cp.leaseSetKey", "");
}

View File

@@ -28,6 +28,7 @@ import net.i2p.data.PrivateKeyFile;
import net.i2p.data.SessionKey;
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
import net.i2p.i2ptunnel.I2PTunnelIRCClient;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.ConcurrentHashSet;
@@ -711,6 +712,11 @@ public class IndexBean {
_booleanOptions.add("i2cp.encryptLeaseSet");
}
/** @since 0.8.9 */
public void setDCC(String moo) {
_booleanOptions.add(I2PTunnelIRCClient.PROP_DCC);
}
protected static final String PROP_ENABLE_ACCESS_LIST = "i2cp.enableAccessList";
protected static final String PROP_ENABLE_BLACKLIST = "i2cp.enableBlackList";
@@ -1020,13 +1026,28 @@ public class IndexBean {
else
config.setProperty("interface", "");
}
if ("ircclient".equals(_type)) {
boolean dcc = _booleanOptions.contains(I2PTunnelIRCClient.PROP_DCC);
config.setProperty("option." + I2PTunnelIRCClient.PROP_DCC,
"" + dcc);
// add some sane server options since they aren't in the GUI (yet)
if (dcc) {
config.setProperty("options." + PROP_MAX_CONNS_MIN, "3");
config.setProperty("options." + PROP_MAX_CONNS_HOUR, "10");
config.setProperty("options." + PROP_MAX_TOTAL_CONNS_MIN, "5");
config.setProperty("options." + PROP_MAX_TOTAL_CONNS_HOUR, "25");
}
}
return config;
}
private static final String _noShowOpts[] = {
"inbound.length", "outbound.length", "inbound.lengthVariance", "outbound.lengthVariance",
"inbound.backupQuantity", "outbound.backupQuantity", "inbound.quantity", "outbound.quantity",
"inbound.nickname", "outbound.nickname", "i2p.streaming.connectDelay", "i2p.streaming.maxWindowSize"
"inbound.nickname", "outbound.nickname", "i2p.streaming.connectDelay", "i2p.streaming.maxWindowSize",
I2PTunnelIRCClient.PROP_DCC
};
private static final String _booleanClientOpts[] = {
"i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen"
@@ -1048,6 +1069,7 @@ public class IndexBean {
PROP_MAX_TOTAL_CONNS_MIN, PROP_MAX_TOTAL_CONNS_HOUR, PROP_MAX_TOTAL_CONNS_DAY,
PROP_MAX_STREAMS
};
protected static final Set _noShowSet = new HashSet(64);
static {
_noShowSet.addAll(Arrays.asList(_noShowOpts));