Files
i2p.i2p/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
jrandom b5a25801b4 2005-10-26 jrandom
* In Syndie, propogate the subject and tags in a reply, and show the parent
      post on the edit page for easy quoting.  (thanks identiguy and CofE!)
    * Streamline some netDb query handling to run outside the jobqueue -
      which means they'll run on the particular SSU thread that handles the
      message.  This should help out heavily loaded netDb peers.
2005-10-28 22:26:47 +00:00

1269 lines
48 KiB
Java

/*
* I2PTunnel
* (c) 2003 - 2004 mihi
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* In addition, as a special exception, mihi gives permission to link
* the code of this program with the proprietary Java implementation
* provided by Sun (or other vendors as well), and distribute linked
* combinations including the two. You must obey the GNU General
* Public License in all respects for all of the code used other than
* the proprietary Java implementation. If you modify this file, you
* may extend this exception to your version of the file, but you are
* not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
package net.i2p.i2ptunnel;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.client.naming.NamingService;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel;
import net.i2p.util.EventDispatcher;
import net.i2p.util.EventDispatcherImpl;
import net.i2p.util.Log;
public class I2PTunnel implements Logging, EventDispatcher {
private Log _log;
private EventDispatcherImpl _event;
private I2PAppContext _context;
private static long __tunnelId = 0;
private long _tunnelId;
private Properties _clientOptions;
private List _sessions;
public static final int PACKET_DELAY = 100;
public boolean ownDest = false;
public String port = System.getProperty(I2PClient.PROP_TCP_PORT, "7654");
public String host = System.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
public String listenHost = host;
public long readTimeout = -1;
private static final String nocli_args[] = { "-nocli", "-die"};
private List tasks = new ArrayList();
private int next_task_id = 1;
private Set listeners = new HashSet();
public static void main(String[] args) throws IOException {
new I2PTunnel(args);
}
public I2PTunnel() {
this(nocli_args);
}
public I2PTunnel(String[] args) {
this(args, null);
}
public I2PTunnel(String[] args, ConnectionEventListener lsnr) {
_context = I2PAppContext.getGlobalContext(); // new I2PAppContext();
_tunnelId = ++__tunnelId;
_log = _context.logManager().getLog(I2PTunnel.class);
_event = new EventDispatcherImpl();
Properties p = new Properties();
p.putAll(System.getProperties());
_clientOptions = p;
_sessions = new ArrayList(1);
addConnectionEventListener(lsnr);
boolean gui = true;
boolean checkRunByE = true;
boolean cli = true;
boolean dontDie = true;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-die")) {
dontDie = false;
gui = false;
cli = false;
checkRunByE = false;
} else if (args[i].equals("-nogui")) {
gui = false;
_log.warn(getPrefix() + "The `-nogui' option of I2PTunnel is deprecated.\n"
+ "Use `-cli', `-nocli' (aka `-wait') or `-die' instead.");
} else if (args[i].equals("-cli")) {
gui = false;
cli = true;
checkRunByE = false;
} else if (args[i].equals("-nocli") || args[i].equals("-wait")) {
gui = false;
cli = false;
checkRunByE = false;
} else if (args[i].equals("-e")) {
runCommand(args[i + 1], this);
i++;
if (checkRunByE) {
checkRunByE = false;
cli = false;
}
} else if (new File(args[i]).exists()) {
runCommand("run " + args[i], this);
} else {
System.out.println("Unknown parameter " + args[i]);
}
}
if (gui) {
new I2PTunnelGUI(this);
} else if (cli) {
try {
System.out.println("Enter 'help' for help.");
BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.print("I2PTunnel>");
String cmd = r.readLine();
if (cmd == null) break;
runCommand(cmd, this);
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
while (dontDie) {
synchronized (this) {
try {
wait();
} catch (InterruptedException ie) {
}
}
}
}
List getSessions() {
synchronized (_sessions) {
return new ArrayList(_sessions);
}
}
void addSession(I2PSession session) {
if (session == null) return;
synchronized (_sessions) {
if (!_sessions.contains(session))
_sessions.add(session);
}
}
void removeSession(I2PSession session) {
if (session == null) return;
synchronized (_sessions) {
_sessions.remove(session);
}
}
public Properties getClientOptions() { return _clientOptions; }
private void addtask(I2PTunnelTask tsk) {
tsk.setTunnel(this);
if (tsk.isOpen()) {
tsk.setId(next_task_id);
next_task_id++;
synchronized (tasks) {
tasks.add(tsk);
}
}
}
/** java 1.3 vs 1.4 :)
*/
private static String[] split(String src, String delim) {
StringTokenizer tok = new StringTokenizer(src, delim);
String vals[] = new String[tok.countTokens()];
for (int i = 0; i < vals.length; i++)
vals[i] = tok.nextToken();
return vals;
}
public void runCommand(String cmd, Logging l) {
if (cmd.indexOf(" ") == -1) cmd += " ";
int iii = cmd.indexOf(" ");
String cmdname = cmd.substring(0, iii).toLowerCase();
String allargs = cmd.substring(iii + 1);
String[] args = split(allargs, " "); // .split(" "); // java 1.4
if ("help".equals(cmdname)) {
runHelp(l);
} else if ("clientoptions".equals(cmdname)) {
runClientOptions(args, l);
} else if ("server".equals(cmdname)) {
runServer(args, l);
} else if ("httpserver".equals(cmdname)) {
runHttpServer(args, l);
} else if ("textserver".equals(cmdname)) {
runTextServer(args, l);
} else if ("client".equals(cmdname)) {
runClient(args, l);
} else if ("httpclient".equals(cmdname)) {
runHttpClient(args, l);
} else if ("ircclient".equals(cmdname)) {
runIrcClient(args, l);
} else if ("sockstunnel".equals(cmdname)) {
runSOCKSTunnel(args, l);
} else if ("config".equals(cmdname)) {
runConfig(args, l);
} else if ("listen_on".equals(cmdname)) {
runListenOn(args, l);
} else if ("read_timeout".equals(cmdname)) {
runReadTimeout(args, l);
} else if ("genkeys".equals(cmdname)) {
runGenKeys(args, l);
} else if ("gentextkeys".equals(cmdname)) {
runGenTextKeys(l);
} else if (cmdname.equals("quit")) {
runQuit(l);
} else if (cmdname.equals("list")) {
runList(l);
} else if (cmdname.equals("close")) {
runClose(args, l);
} else if (cmdname.equals("run")) {
runRun(args, l);
} else if (cmdname.equals("lookup")) {
runLookup(args, l);
} else if (cmdname.equals("ping")) {
runPing(allargs, l);
} else if (cmdname.equals("owndest")) {
runOwnDest(args, l);
} else {
l.log("Unknown command [" + cmdname + "]");
}
}
/**
* Display help information through the given logger.
*
* Does not fire any events to the logger
*
* @param l logger to receive events and output
*/
public void runHelp(Logging l) {
l.log("Command list:");
l.log("config <i2phost> <i2pport>");
l.log("listen_on <ip>");
l.log("read_timeout <msecs>");
l.log("owndest yes|no");
l.log("ping <args>");
l.log("server <host> <port> <privkeyfile>");
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>");
l.log("textserver <host> <port> <privkey>");
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
l.log("gentextkeys");
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("ircclient <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
l.log("lookup <name>");
l.log("quit");
l.log("close [forced] <jobnumber>|all");
l.log("list");
l.log("run <commandfile>");
}
/**
* Configure the extra I2CP options to use in any subsequent I2CP sessions.
* Usage: "clientoptions[ key=value]*" .
*
* Sets the event "clientoptions_onResult" = "ok" after completion.
*
* @param args each args[i] is a key=value pair to add to the options
* @param l logger to receive events and output
*/
public void runClientOptions(String args[], Logging l) {
_clientOptions.clear();
if (args != null) {
for (int i = 0; i < args.length; i++) {
int index = args[i].indexOf('=');
if (index <= 0) continue;
String key = args[i].substring(0, index);
String val = args[i].substring(index+1);
_clientOptions.setProperty(key, val);
}
}
notifyEvent("clientoptions_onResult", "ok");
}
/**
* Run the server pointing at the host and port specified using the private i2p
* destination loaded from the specified file. <p />
*
* Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
* Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after
* 'ok'). So, success = serverTaskId != -1 and openServerResult = ok.
*
* @param args {hostname, portNumber, privKeyFilename}
* @param l logger to receive events and output
*/
public void runServer(String args[], Logging l) {
if (args.length == 3) {
InetAddress serverHost = null;
int portNum = -1;
File privKeyFile = null;
try {
serverHost = InetAddress.getByName(args[0]);
} catch (UnknownHostException uhe) {
l.log("unknown host");
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
try {
portNum = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
privKeyFile = new File(args[2]);
if (!privKeyFile.canRead()) {
l.log("private key file does not exist");
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[2]);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
I2PTunnelServer serv = new I2PTunnelServer(serverHost, portNum, privKeyFile, args[2], l, (EventDispatcher) this, this);
serv.setReadTimeout(readTimeout);
serv.startRunning();
addtask(serv);
notifyEvent("serverTaskId", new Integer(serv.getId()));
return;
} else {
l.log("server <host> <port> <privkeyfile>");
l.log(" creates a server that sends all incoming data\n" + " of its destination to host:port.");
notifyEvent("serverTaskId", new Integer(-1));
}
}
/**
* Run the HTTP server pointing at the host and port specified using the private i2p
* destination loaded from the specified file, replacing the HTTP headers
* so that the Host: specified is the one spoofed. <p />
*
* Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
* Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after
* 'ok'). So, success = serverTaskId != -1 and openServerResult = ok.
*
* @param args {hostname, portNumber, spoofedHost, privKeyFilename}
* @param l logger to receive events and output
*/
public void runHttpServer(String args[], Logging l) {
if (args.length == 4) {
InetAddress serverHost = null;
int portNum = -1;
File privKeyFile = null;
try {
serverHost = InetAddress.getByName(args[0]);
} catch (UnknownHostException uhe) {
l.log("unknown host");
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
try {
portNum = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
String spoofedHost = args[2];
privKeyFile = new File(args[3]);
if (!privKeyFile.canRead()) {
l.log("private key file does not exist");
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
I2PTunnelHTTPServer serv = new I2PTunnelHTTPServer(serverHost, portNum, privKeyFile, args[3], spoofedHost, l, (EventDispatcher) this, this);
serv.setReadTimeout(readTimeout);
serv.startRunning();
addtask(serv);
notifyEvent("serverTaskId", new Integer(serv.getId()));
return;
} else {
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>");
l.log(" creates an HTTP server that sends all incoming data\n"
+ " of its destination to host:port., filtering the HTTP\n"
+ " headers so it looks like the request is to the spoofed host.");
notifyEvent("serverTaskId", new Integer(-1));
}
}
/**
* Run the server pointing at the host and port specified using the private i2p
* destination loaded from the given base64 stream. <p />
*
* Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
* Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after
* 'ok'). So, success = serverTaskId != -1 and openServerResult = ok.
*
* @param args {hostname, portNumber, privKeyBase64}
* @param l logger to receive events and output
*/
public void runTextServer(String args[], Logging l) {
if (args.length == 3) {
InetAddress serverHost = null;
int portNum = -1;
try {
serverHost = InetAddress.getByName(args[0]);
} catch (UnknownHostException uhe) {
l.log("unknown host");
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
try {
portNum = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
I2PTunnelServer serv = new I2PTunnelServer(serverHost, portNum, args[2], l, (EventDispatcher) this, this);
serv.setReadTimeout(readTimeout);
serv.startRunning();
addtask(serv);
notifyEvent("serverTaskId", new Integer(serv.getId()));
} else {
l.log("textserver <host> <port> <privkey>");
l.log(" creates a server that sends all incoming data\n" + " of its destination to host:port.");
notifyEvent("textserverTaskId", new Integer(-1));
}
}
/**
* Run the client on the given port number pointing at the specified destination
* (either the base64 of the destination or file:fileNameContainingDestination).
*
* Sets the event "clientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
* Also sets the event "openClientResult" = "error" or "ok" (before setting the value to "ok" it also
* adds "Ready! Port #" to the logger as well). In addition, it will also set "clientLocalPort" =
* Integer port number if the client is listening
* sharedClient parameter is a String "true" or "false"
*
* @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient]}
* @param l logger to receive events and output
*/
public void runClient(String args[], Logging l) {
boolean isShared = true;
if (args.length == 3)
isShared = Boolean.valueOf(args[2].trim()).booleanValue();
if ( (args.length == 2) || (args.length == 3) ) {
int portNum = -1;
try {
portNum = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
notifyEvent("clientTaskId", new Integer(-1));
return;
}
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this);
addtask(task);
notifyEvent("clientTaskId", new Integer(task.getId()));
} catch (IllegalArgumentException iae) {
_log.error(getPrefix() + "Invalid I2PTunnel config to create a client [" + host + ":"+ port + "]", iae);
l.log("Invalid I2PTunnel configuration [" + host + ":" + port + "]");
notifyEvent("clientTaskId", new Integer(-1));
}
} else {
l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>[ <sharedClient>]");
l.log(" creates a client that forwards port to the pubkey.\n"
+ " use 0 as port to get a free port assigned. If you specify\n"
+ " a comma delimited list of pubkeys, it will rotate among them\n"
+ " randomlyl. sharedClient indicates if this client shares \n"
+ " with other clients (true of false)");
notifyEvent("clientTaskId", new Integer(-1));
}
}
/**
* Run an HTTP client on the given port number
*
* Sets the event "httpclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
* Also sets "httpclientStatus" = "ok" or "error" after the client tunnel has started.
* parameter sharedClient is a String, either "true" or "false"
*
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
* @param l logger to receive events and output
*/
public void runHttpClient(String args[], Logging l) {
if (args.length >= 1 && args.length <= 3) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
notifyEvent("httpclientTaskId", new Integer(-1));
return;
}
String proxy = "squid.i2p";
boolean isShared = true;
if (args.length > 1) {
if ("true".equalsIgnoreCase(args[1].trim())) {
isShared = true;
if (args.length == 3)
proxy = args[2];
} else if ("false".equalsIgnoreCase(args[1].trim())) {
_log.warn("args[1] == [" + args[1] + "] and rejected explicitly");
isShared = false;
if (args.length == 3)
proxy = args[2];
} else if (args.length == 3) {
isShared = false; // not "true"
proxy = args[2];
_log.warn("args[1] == [" + args[1] + "] but rejected");
} else {
// isShared not specified, default to true
isShared = true;
proxy = args[1];
}
}
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelHTTPClient(port, l, ownDest, proxy, (EventDispatcher) this, this);
addtask(task);
notifyEvent("httpclientTaskId", new Integer(task.getId()));
} catch (IllegalArgumentException iae) {
_log.error(getPrefix() + "Invalid I2PTunnel config to create an httpclient [" + host + ":"+ port + "]", iae);
l.log("Invalid I2PTunnel configuration [" + host + ":" + port + "]");
notifyEvent("httpclientTaskId", new Integer(-1));
}
} else {
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
l.log(" creates a client that distributes HTTP requests.");
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
l.log(" <proxy> (optional) indicates a proxy server to be used");
l.log(" when trying to access an address out of the .i2p domain");
l.log(" (the default proxy is squid.i2p).");
notifyEvent("httpclientTaskId", new Integer(-1));
}
}
/**
* Run an IRC client on the given port number
*
* Sets the event "ircclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
* Also sets "ircclientStatus" = "ok" or "error" after the client tunnel has started.
* parameter sharedClient is a String, either "true" or "false"
*
* @param args {portNumber,destinationBase64 or "file:filename" [, sharedClient]}
* @param l logger to receive events and output
*/
public void runIrcClient(String args[], Logging l) {
if (args.length >= 2 && args.length <= 3) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
notifyEvent("ircclientTaskId", new Integer(-1));
return;
}
boolean isShared = true;
if (args.length > 2) {
if ("true".equalsIgnoreCase(args[2].trim())) {
isShared = true;
} else if ("false".equalsIgnoreCase(args[2].trim())) {
_log.warn("args[2] == [" + args[2] + "] and rejected explicitly");
isShared = false;
} else {
// isShared not specified, default to true
isShared = true;
}
}
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelIRCClient(port, args[1],l, ownDest, (EventDispatcher) this, this);
addtask(task);
notifyEvent("ircclientTaskId", new Integer(task.getId()));
} catch (IllegalArgumentException iae) {
_log.error(getPrefix() + "Invalid I2PTunnel config to create an ircclient [" + host + ":"+ port + "]", iae);
l.log("Invalid I2PTunnel configuration [" + host + ":" + port + "]");
notifyEvent("ircclientTaskId", new Integer(-1));
}
} else {
l.log("ircclient <port> [<sharedClient>]");
l.log(" creates a client that filter IRC protocol.");
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
notifyEvent("ircclientTaskId", new Integer(-1));
}
}
/**
* Run an SOCKS tunnel on the given port number
*
* Sets the event "sockstunnelTaskId" = Integer(taskId) after the
* tunnel has been started (or -1 on error). Also sets
* "openSOCKSTunnelResult" = "ok" or "error" after the client tunnel has
* started.
*
* @param args {portNumber}
* @param l logger to receive events and output
*/
public void runSOCKSTunnel(String args[], Logging l) {
if (args.length >= 1 && args.length <= 2) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
notifyEvent("sockstunnelTaskId", new Integer(-1));
return;
}
I2PTunnelTask task;
task = new I2PSOCKSTunnel(port, l, ownDest, (EventDispatcher) this, this);
addtask(task);
notifyEvent("sockstunnelTaskId", new Integer(task.getId()));
} else {
l.log("sockstunnel <port>");
l.log(" creates a tunnel that distributes SOCKS requests.");
notifyEvent("sockstunnelTaskId", new Integer(-1));
}
}
/**
* Specify the i2cp host and port
*
* Sets the event "configResult" = "ok" or "error" after the configuration has been specified
*
* @param args {hostname, portNumber}
* @param l logger to receive events and output
*/
public void runConfig(String args[], Logging l) {
if (args.length == 2) {
host = args[0];
listenHost = host;
port = args[1];
notifyEvent("configResult", "ok");
} else {
l.log("config <i2phost> <i2pport>");
l.log(" sets the connection to the i2p router.");
notifyEvent("configResult", "error");
}
}
/**
* Specify whether to use its own destination for each outgoing tunnel
*
* Sets the event "owndestResult" = "ok" or "error" after the configuration has been specified
*
* @param args {yes or no}
* @param l logger to receive events and output
*/
public void runOwnDest(String args[], Logging l) {
if (args.length == 1 && (args[0].equalsIgnoreCase("yes") || args[0].equalsIgnoreCase("no"))) {
ownDest = args[0].equalsIgnoreCase("yes");
notifyEvent("owndestResult", "ok");
} else {
l.log("owndest yes|no");
l.log(" Specifies whether to use its own destination \n" + " for each outgoing tunnel");
notifyEvent("owndestResult", "error");
}
}
/**
* Specify the hostname / IP address of the interface that the tunnels should bind to
*
* Sets the event "listen_onResult" = "ok" or "error" after the interface has been specified
*
* @param args {hostname}
* @param l logger to receive events and output
*/
public void runListenOn(String args[], Logging l) {
if (args.length == 1) {
listenHost = args[0];
notifyEvent("listen_onResult", "ok");
} else {
l.log("listen_on <ip>");
l.log(" sets the interface to listen for the I2PClient.");
notifyEvent("listen_onResult", "error");
}
}
/**
* Specify the read timeout going to be used for newly-created I2PSockets
*
* Sets the event "read_timeoutResult" = "ok" or "error" after the interface has been specified
*
* @param args {hostname}
* @param l logger to receive events and output
*/
public void runReadTimeout(String args[], Logging l) {
if (args.length == 1) {
try {
readTimeout = Long.parseLong(args[0]);
} catch (NumberFormatException e) {
notifyEvent("read_timeoutResult", "error");
}
notifyEvent("read_timeoutResult", "ok");
} else {
l.log("read_timeout <msecs>");
l.log(" sets the read timeout (in milliseconds) for I2P connections\n"
+" Negative values will make the connections wait forever");
notifyEvent("read_timeoutResult", "error");
}
}
/**
* Generate a new keypair
*
* Sets the event "genkeysResult" = "ok" or "error" after the generation is complete
*
* @param args {privateKeyFilename, publicKeyFilename} or {privateKeyFilename}
* @param l logger to receive events and output
*/
public void runGenKeys(String args[], Logging l) {
OutputStream pubdest = null;
if (args.length == 2) {
try {
pubdest = new FileOutputStream(args[1]);
} catch (IOException ioe) {
l.log("Error opening output stream");
_log.error(getPrefix() + "Error generating keys to out", ioe);
notifyEvent("genkeysResult", "error");
return;
}
} else if (args.length != 1) {
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
l.log(" creates a new keypair and prints the public key.\n"
+ " if pubkeyfile is given, saves the public key there." + "\n"
+ " if the privkeyfile already exists, just print/save" + "the pubkey.");
notifyEvent("genkeysResult", "error");
}
try {
File privKeyFile = new File(args[0]);
if (privKeyFile.exists()) {
l.log("File already exists.");
showKey(new FileInputStream(privKeyFile), pubdest, l);
} else {
makeKey(new FileOutputStream(privKeyFile), pubdest, l);
}
notifyEvent("genkeysResult", "ok");
} catch (IOException ioe) {
l.log("Error generating keys - " + ioe.getMessage());
notifyEvent("genkeysResult", "error");
_log.error(getPrefix() + "Error generating keys", ioe);
}
}
/**
* Generate a new keypair
*
* Sets the event "privateKey" = base64 of the privateKey stream and
* sets the event "publicDestination" = base64 of the destination
*
* @param l logger to receive events and output
*/
public void runGenTextKeys(Logging l) {
ByteArrayOutputStream privkey = new ByteArrayOutputStream(512);
ByteArrayOutputStream pubkey = new ByteArrayOutputStream(512);
makeKey(privkey, pubkey, l);
l.log("Private key: " + Base64.encode(privkey.toByteArray()));
notifyEvent("privateKey", Base64.encode(privkey.toByteArray()));
notifyEvent("publicDestination", Base64.encode(pubkey.toByteArray()));
}
/**
* Exit the JVM if there are no more tasks left running. If there are tunnels
* running, it returns.
*
* Sets the event "quitResult" = "error" if there are tasks running (but if there
* aren't, well, there's no point in setting the quitResult to "ok", now is there?)
*
* @param l logger to receive events and output
*/
public void runQuit(Logging l) {
purgetasks(l);
synchronized (tasks) {
if (tasks.isEmpty()) {
System.exit(0);
}
}
l.log("There are running tasks. Try 'list'.");
notifyEvent("quitResult", "error");
}
/**
* Retrieve a list of currently running tasks
*
* Sets the event "listDone" = "done" after dumping the tasks to
* the logger
*
* @param l logger to receive events and output
*/
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());
}
}
notifyEvent("listDone", "done");
}
/**
* Close the given task (or all tasks), optionally forcing them to die a hard
* death
*
* Sets the event "closeResult" = "ok" after the closing is complete
*
* @param args {jobNumber}, {"forced", jobNumber}, {"forced", "all"}
* @param l logger to receive events and output
*/
public void runClose(String args[], Logging l) {
if (args.length == 0 || args.length > 2) {
l.log("close [forced] <jobnumber>|all");
l.log(" stop running tasks. either only one or all.\n"
+ " use 'forced' to also stop tasks with active connections.\n"
+ " use the 'list' command to show the job numbers");
notifyEvent("closeResult", "error");
} else {
int argindex = 0; // parse optional 'forced' keyword
boolean forced = false;
if (args[argindex].equalsIgnoreCase("forced")) {
forced = true;
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);
if (!closetask(t, forced, l)) {
notifyEvent("closeResult", "error");
error = true;
} else if (!error) { // If there's an error, don't hide it
notifyEvent("closeResult", "ok");
}
}
} else {
try {
if (!closetask(Integer.parseInt(args[argindex]), forced, l)) {
notifyEvent("closeResult", "error");
} else {
notifyEvent("closeResult", "ok");
}
} catch (NumberFormatException ex) {
l.log("Incorrect job number: " + args[argindex]);
notifyEvent("closeResult", "error");
}
}
}
}
/**
* Run all of the commands in the given file (one command per line)
*
* Sets the event "runResult" = "ok" or "error" after the closing is complete
*
* @param args {filename}
* @param l logger to receive events and output
*/
public void runRun(String args[], Logging l) {
if (args.length == 1) {
try {
BufferedReader br = new BufferedReader(new FileReader(args[0]));
String line;
while ((line = br.readLine()) != null) {
runCommand(line, l);
}
br.close();
notifyEvent("runResult", "ok");
} catch (IOException ioe) {
l.log("IO error running the file");
_log.error(getPrefix() + "Error running the file", ioe);
notifyEvent("runResult", "error");
}
} else {
l.log("run <commandfile>");
l.log(" loads commandfile and runs each line in it. \n"
+ " You can also give the filename on the commandline.");
notifyEvent("runResult", "error");
}
}
/**
* Perform a lookup of the name specified
*
* Sets the event "lookupResult" = base64 of the destination, or an error message
*
* @param args {name}
* @param l logger to receive events and output
*/
public void runLookup(String args[], Logging l) {
if (args.length != 1) {
l.log("lookup <name>");
l.log(" try to resolve the name into a destination key");
notifyEvent("lookupResult", "invalidUsage");
} else {
String target = args[0];
try {
Destination dest = destFromName(args[0]);
if (dest == null) {
l.log("Unknown host");
notifyEvent("lookupResult", "unkown host");
} else {
l.log(dest.toBase64());
notifyEvent("lookupResult", dest.toBase64());
}
} catch (DataFormatException dfe) {
l.log("Unknown or invalid host");
notifyEvent("lookupResult", "invalid host");
}
}
}
/**
* Start up a ping task with the specified args (currently supporting -ns, -h, -l)
*
* Sets the event "pingTaskId" = Integer of the taskId, or -1
*
* @param allargs arguments to pass to the I2Ping task
* @param l logger to receive events and output
*/
public void runPing(String allargs, Logging l) {
if (allargs.length() != 0) {
I2PTunnelTask task;
// pings always use the main destination
task = new I2Ping(allargs, l, false, (EventDispatcher) this, this);
addtask(task);
notifyEvent("pingTaskId", new Integer(task.getId()));
} else {
l.log("ping <opts> <dest>");
l.log("ping <opts> -h");
l.log("ping <opts> -l <destlistfile>");
l.log(" Tests communication with peers.\n" + " opts can be -ns (nosync) or not.");
notifyEvent("pingTaskId", new Integer(-1));
}
}
/**
* Helper method to actually close the given task number (optionally forcing
* closure)
*
*/
private boolean closetask(int num, boolean forced, Logging l) {
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();
int id = t.getId();
_log.debug(getPrefix() + "closetask(): parsing task " + id + " (" + t.toString() + ")");
if (id == num) {
closed = closetask(t, forced, l);
break;
} else if (id > num) {
break;
}
}
}
return closed;
}
/**
* Helper method to actually close the given task number
* (optionally forcing closure)
*
*/
private boolean closetask(I2PTunnelTask t, boolean forced, Logging l) {
l.log("Closing task " + t.getId() + (forced ? " forced..." : "..."));
if (t.close(forced)) {
l.log("Task " + t.getId() + " closed.");
return true;
}
return false;
}
/**
* Helper task to remove closed / completed tasks.
*
*/
private void purgetasks(Logging l) {
synchronized (tasks) {
for (Iterator it = tasks.iterator(); it.hasNext();) {
I2PTunnelTask t = (I2PTunnelTask) it.next();
if (!t.isOpen()) {
_log.debug(getPrefix() + "Purging inactive tunnel: [" + t.getId() + "] " + t.toString());
it.remove();
}
}
}
}
/**
* Log the given message (using both the logging subsystem and standard output...)
*
*/
public void log(String s) {
System.out.println(s);
_log.info(getPrefix() + "Display: " + s);
}
/**
* Create a new destination, storing the destination and its private keys where
* instructed
*
* @param writeTo location to store the private keys
* @param pubDest location to store the destination
* @param l logger to send messages to
*/
public static void makeKey(OutputStream writeTo, OutputStream pubDest, Logging l) {
try {
l.log("Generating new keys...");
ByteArrayOutputStream priv = new ByteArrayOutputStream(), pub = new ByteArrayOutputStream();
I2PClient client = I2PClientFactory.createClient();
Destination d = client.createDestination(writeTo);
l.log("Secret key saved.");
l.log("Public key: " + d.toBase64());
writeTo.flush();
writeTo.close();
writePubKey(d, pubDest, l);
} catch (I2PException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* Read in the given destination, display it, and write it to the given location
*
* @param readFrom stream to read the destination from
* @param pubDest stream to write the destination to
* @param l logger to send messages to
*/
public static void showKey(InputStream readFrom, OutputStream pubDest, Logging l) {
try {
I2PClient client = I2PClientFactory.createClient();
Destination d = new Destination();
d.readBytes(readFrom);
l.log("Public key: " + d.toBase64());
readFrom.close();
writePubKey(d, pubDest, l);
} catch (I2PException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* Write out the destination to the stream
*
* @param d Destination to write
* @param o stream to write the destination to
* @param l logger to send messages to
*/
private static void writePubKey(Destination d, OutputStream o, Logging l) throws I2PException, IOException {
if (o == null) return;
d.writeBytes(o);
l.log("Public key saved.");
}
/**
* Generates a Destination from a name. Now only supports base64
* names - may support naming servers later. "file:<filename>" is
* also supported, where filename is a file that either contains a
* binary Destination structure or the Base64 encoding of that
* structure.
*/
public static Destination destFromName(String name) throws DataFormatException {
if ((name == null) || (name.trim().length() <= 0)) throw new DataFormatException("Empty destination provided");
I2PAppContext ctx = I2PAppContext.getGlobalContext();
Log log = ctx.logManager().getLog(I2PTunnel.class);
if (name.startsWith("file:")) {
Destination result = new Destination();
byte content[] = null;
FileInputStream in = null;
try {
in = new FileInputStream(name.substring("file:".length()));
byte buf[] = new byte[1024];
int read = DataHelper.read(in, buf);
content = new byte[read];
System.arraycopy(buf, 0, content, 0, read);
} catch (IOException ioe) {
System.out.println(ioe.getMessage());
return null;
} finally {
if (in != null) try {
in.close();
} catch (IOException io) {
}
}
try {
result.fromByteArray(content);
return result;
} catch (Exception ex) {
if (log.shouldLog(Log.INFO))
log.info("File is not a binary destination - trying base64");
try {
byte decoded[] = Base64.decode(new String(content));
result.fromByteArray(decoded);
return result;
} catch (DataFormatException dfe) {
if (log.shouldLog(Log.WARN))
log.warn("File is not a base64 destination either - failing!");
return null;
}
}
} else {
// ask naming service
NamingService inst = ctx.namingService();
return inst.lookup(name);
}
}
public void addConnectionEventListener(ConnectionEventListener lsnr) {
if (lsnr == null) return;
synchronized (listeners) {
listeners.add(lsnr);
}
}
public void removeConnectionEventListener(ConnectionEventListener lsnr) {
if (lsnr == null) return;
synchronized (listeners) {
listeners.remove(lsnr);
}
}
private String getPrefix() { return '[' + _tunnelId + "]: "; }
public I2PAppContext getContext() { return _context; }
/**
* Call this whenever we lose touch with the router involuntarily (aka the router
* is off / crashed / etc)
*
*/
void routerDisconnected() {
_log.error(getPrefix() + "Router disconnected - firing notification events");
synchronized (listeners) {
for (Iterator iter = listeners.iterator(); iter.hasNext();) {
ConnectionEventListener lsnr = (ConnectionEventListener) iter.next();
if (lsnr != null) lsnr.routerDisconnected();
}
}
}
/**
* Callback routine to find out
*/
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);
}
}