/* * 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.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.lang.reflect.Constructor; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicLong; import gnu.getopt.Getopt; import gnu.getopt.LongOpt; 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.I2PSessionException; import net.i2p.client.I2PSimpleClient; 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.I2PSOCKSIRCTunnel; import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel; import net.i2p.i2ptunnel.streamr.StreamrConsumer; import net.i2p.i2ptunnel.streamr.StreamrProducer; import net.i2p.util.EventDispatcherImpl; import net.i2p.util.Log; import net.i2p.util.OrderedProperties; /** * 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 extends EventDispatcherImpl implements Logging { private final Log _log; private final I2PAppContext _context; private static final AtomicLong __tunnelId = new AtomicLong(); private final long _tunnelId; private final Properties _clientOptions; private final Set<I2PSession> _sessions; public static final int PACKET_DELAY = 100; public boolean ownDest = false; /** the I2CP port, non-null */ public String port = System.getProperty(I2PClient.PROP_TCP_PORT, "7654"); /** the I2CP host, non-null */ public String host = System.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1"); /** the listen-on host. Sadly the listen-on port does not have a field. */ public String listenHost = host; public long readTimeout = -1; private static final String nocli_args[] = { "-nocli", "-die"}; private final List<I2PTunnelTask> tasks = new CopyOnWriteArrayList<I2PTunnelTask>(); private int next_task_id = 1; private final Set<ConnectionEventListener> listeners = new CopyOnWriteArraySet<ConnectionEventListener>(); private static final int NOGUI = 99999; private static final LongOpt[] longopts = new LongOpt[] { new LongOpt("cli", LongOpt.NO_ARGUMENT, null, 'c'), new LongOpt("die", LongOpt.NO_ARGUMENT, null, 'd'), new LongOpt("gui", LongOpt.NO_ARGUMENT, null, 'g'), new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h'), new LongOpt("nocli", LongOpt.NO_ARGUMENT, null, 'w'), new LongOpt("nogui", LongOpt.NO_ARGUMENT, null, NOGUI), new LongOpt("wait", LongOpt.NO_ARGUMENT, null, 'w') }; /** @since 0.9.17 */ private enum CloseMode { NORMAL, FORCED, DESTROY } public static void main(String[] args) { try { new I2PTunnel(args); } catch (IllegalArgumentException iae) { System.err.println(iae.toString()); System.exit(1); } } /** * Standard constructor for embedded, uses args "-nocli -die" to return immediately */ public I2PTunnel() { this(nocli_args); } /** * See usage() for options * @throws IllegalArgumentException */ public I2PTunnel(String[] args) { this(args, null); } /** * See usage() for options * @param lsnr may be null * @throws IllegalArgumentException */ public I2PTunnel(String[] args, ConnectionEventListener lsnr) { super(); _context = I2PAppContext.getGlobalContext(); // new I2PAppContext(); _tunnelId = __tunnelId.incrementAndGet(); _log = _context.logManager().getLog(I2PTunnel.class); // as of 0.8.4, include context properties Properties p = _context.getProperties(); _clientOptions = p; _sessions = new CopyOnWriteArraySet<I2PSession>(); addConnectionEventListener(lsnr); boolean gui = true; boolean checkRunByE = true; boolean cli = true; boolean dontDie = true; boolean error = false; List<String> eargs = null; Getopt g = new Getopt("i2ptunnel", args, "d::n:c::w::e:h::", longopts); int c; while ((c = g.getopt()) != -1) { switch (c) { case 'd': // -d, -die, --die dontDie = false; gui = false; cli = false; checkRunByE = false; break; case 'n': // -noc, -nog, -nocli, -nogui String a = g.getOptarg(); if (a.startsWith("oc")) { gui = false; cli = false; checkRunByE = false; break; } else if (a.startsWith("og")) { // fall thru } else { error = true; break; } // fall thru for -nogui only case NOGUI: // --nogui gui = false; if (_log.shouldLog(Log.WARN)) _log.warn(getPrefix() + "The `-nogui' option of I2PTunnel is deprecated.\n" + "Use `-cli', `-nocli' (aka `-wait') or `-die' instead."); case 'c': // -c, -cli, --cli gui = false; cli = true; checkRunByE = false; break; case 'w': // -w, -wait, --nocli gui = false; cli = false; checkRunByE = false; break; case 'e': if (eargs == null) eargs = new ArrayList<String>(4); eargs.add(g.getOptarg()); if (checkRunByE) { checkRunByE = false; cli = false; } break; case 'h': case '?': case ':': default: error = true; } } int remaining = args.length - g.getOptind(); if (error || remaining > 1) { System.err.println(usage()); throw new IllegalArgumentException(); } if (eargs != null) { for (String arg : eargs) { runCommand(arg, this); } } if (remaining == 1) { String f = args[g.getOptind()]; File file = new File(f); // This is probably just a problem with the options, so // throw from here if (!file.exists()) { System.err.println(usage()); throw new IllegalArgumentException("Command file does not exist: " + f); } runCommand("run " + f, this); } if (gui) { // removed from source, now in i2p.scripts //new I2PTunnelGUI(this); try { Class<?> cls = Class.forName("net.i2p.i2ptunnel.I2PTunnelGUI"); Constructor<?> con = cls.getConstructor(I2PTunnel.class); con.newInstance(this); } catch (Throwable t) { throw new UnsupportedOperationException("GUI is not available, try -cli", t); } } 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; if (cmd.length() <= 0) continue; try { runCommand(cmd, this); } catch (Throwable t) { t.printStackTrace(); } } } catch (IOException ex) { ex.printStackTrace(); } } else if (eargs == null && remaining == 0 && dontDie) { System.err.println(usage()); throw new IllegalArgumentException("Waiting for nothing! Specify gui, cli, command, command file, or die"); } while (dontDie) { synchronized (this) { try { wait(); } catch (InterruptedException ie) { } } } } /** with newlines except for last line */ private static String usage() { // not sure this all makes sense, just documenting what's above return "Usage: i2ptunnel [options] [commandFile]\n" + " Default is to run the GUI.\n" + " commandFile: run all commands in this file\n" + " Options:\n" + " -c, -cli, --cli : run the command line interface\n" + " -d, -die, --die : exit immediately, do not wait for commands to finish\n" + " -e 'command [args]' : run the command\n" + " -h, --help : display this help\n" + " -nocli, --nocli : do not run the command line interface or GUI\n" + " -nogui, --nogui : do not run the GUI\n" + " -w, -wait, --wait : do not run the command line interface or GUI"; } /** * @return A copy, unmodifiable, non-null */ List<I2PSession> getSessions() { if (_sessions.isEmpty()) return Collections.emptyList(); return new ArrayList<I2PSession>(_sessions); } /** * @param session null ok */ void addSession(I2PSession session) { if (session == null) return; boolean added = _sessions.add(session); if (added && _log.shouldLog(Log.INFO)) _log.info(getPrefix() + " session added: " + session, new Exception()); } /** * @param session null ok */ void removeSession(I2PSession session) { if (session == null) return; boolean removed = _sessions.remove(session); if (removed && _log.shouldLog(Log.INFO)) _log.info(getPrefix() + " session removed: " + session, new Exception()); } /** * Generic options used for clients and servers. * NOT a copy, Do NOT modify for per-connection options, make a copy. * @return non-null, NOT a copy, do NOT modify for per-connection options */ public Properties getClientOptions() { return _clientOptions; } private void addtask(I2PTunnelTask tsk) { tsk.setTunnel(this); if (tsk.isOpen()) { tsk.setId(next_task_id); next_task_id++; tasks.add(tsk); if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + " adding task: " + tsk); } else { if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + " not adding task that isn't open: " + 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(Locale.US); 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 ("httpbidirserver".equals(cmdname)) { runHttpBidirServer(args, l); } else if ("ircserver".equals(cmdname)) { runIrcServer(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 ("socksirctunnel".equals(cmdname)) { runSOCKSIRCTunnel(args, l); } else if ("connectclient".equals(cmdname)) { runConnectClient(args, l); } else if ("streamrclient".equals(cmdname)) { runStreamrClient(args, l); } else if ("streamrserver".equals(cmdname)) { runStreamrServer(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 if (cmdname.equals("auth")) { runAuth(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 */ private static void runHelp(Logging l) { l.log("Command list:\n" + // alphabetical please... " auth <username> <password>\n" + " client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]\n" + " clientoptions [-acx] [key=value ]*\n" + " close [forced|destroy] <jobnumber>|all\n" + " config [-s] <i2phost> <i2pport>\n" + " connectclient <port> [<sharedClient>] [<proxy>]\n" + " genkeys <privkeyfile> [<pubkeyfile>]\n" + " gentextkeys\n" + " httpbidirserver <host> <port> <proxyport> <spoofedhost> <privkeyfile>\n" + " httpclient <port> [<sharedClient>] [<proxy>]\n" + " httpserver <host> <port> <spoofedhost> <privkeyfile>\n" + " ircclient <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]\n" + " list\n" + " listen_on <ip>\n" + " lookup <name>\n" + " owndest yes|no\n" + " ping <args>\n" + " quit\n" + " read_timeout <msecs>\n" + " run <commandfile>\n" + " server <host> <port> <privkeyfile>\n" + " socksirctunnel <port> [<sharedClient> [<privKeyFile>]]\n" + " sockstunnel <port>\n" + " streamrclient <host> <port> <destination>\n" + " streamrserver <port> <privkeyfile>\n" + " textserver <host> <port> <privkey>\n"); } /** * Configure the extra I2CP options to use in any subsequent I2CP sessions. * Generic options used for clients and servers * Usage: "clientoptions[ key=value]*" . * * Sets the event "clientoptions_onResult" = "ok" after completion. * * Deprecated To be made private, use setClientOptions(). * This does NOT update a running TunnelTask. * * @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) { if (args != null && args.length > 0) { int i = 0; if (args[0].equals("-a")) { i++; } else if (args[0].equals("-c")) { _clientOptions.clear(); l.log("Client options cleared"); return; } else if (args[0].equals("-x")) { i++; for ( ; i < args.length; i++) { if (_clientOptions.remove(args[i]) != null) l.log("Removed " + args[i]); } return; } else { _clientOptions.clear(); } for ( ; 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); } } else { l.log("Usage:\n" + " clientoptions // show help and list current options\n" + " clientoptions [key=value ]* // sets current options\n" + " clientoptions -a [key=value ]* // adds to current options\n" + " clientoptions -c // clears current options\n" + " clientoptions -x [key ]* // removes listed options\n" + "\nCurrent options:"); Properties p = new OrderedProperties(); p.putAll(_clientOptions); for (Map.Entry<Object, Object> e : p.entrySet()) { l.log(" [" + e.getKey() + "] = [" + e.getValue() + ']'); } } notifyEvent("clientoptions_onResult", "ok"); } /** * Generic options used for clients and servers. * This DOES update a running TunnelTask, but NOT the session. * A more efficient runClientOptions(). * * Defaults in opts properties are not recommended, they may or may not be honored. * * @param opts non-null * @since 0.9.1 */ public void setClientOptions(Properties opts) { for (Iterator<Object> iter = _clientOptions.keySet().iterator(); iter.hasNext();) { Object key = iter.next(); if (!opts.containsKey(key)) iter.remove(); } _clientOptions.putAll(opts); for (I2PTunnelTask task : tasks) { task.optionsUpdated(this); } 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 * @throws IllegalArgumentException on config problem */ 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", Integer.valueOf(-1)); throw new IllegalArgumentException(getPrefix() + "Error resolving " + args[0] + uhe.getMessage()); } 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", Integer.valueOf(-1)); } if (portNum <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[1]); privKeyFile = new File(args[2]); if (!privKeyFile.isAbsolute()) privKeyFile = new File(_context.getConfigDir(), args[2]); if (!privKeyFile.canRead()) { l.log(getPrefix() + "Private key file does not exist or is not readable: " + args[2]); _log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[2]); notifyEvent("serverTaskId", Integer.valueOf(-1)); throw new IllegalArgumentException(getPrefix() + "Cannot open private key file " + args[2]); } I2PTunnelServer serv = new I2PTunnelServer(serverHost, portNum, privKeyFile, args[2], l, this, this); serv.setReadTimeout(readTimeout); serv.startRunning(); addtask(serv); notifyEvent("serverTaskId", Integer.valueOf(serv.getId())); return; } else { l.log("server <host> <port> <privkeyfile>\n" + " creates a server that sends all incoming data\n" + " of its destination to host:port."); notifyEvent("serverTaskId", Integer.valueOf(-1)); } } /** * Same args as runServer * (we should stop duplicating all this code...) * @throws IllegalArgumentException on config problem */ public void runIrcServer(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", Integer.valueOf(-1)); throw new IllegalArgumentException(getPrefix() + "Error resolving " + args[0] + uhe.getMessage()); } 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", Integer.valueOf(-1)); } if (portNum <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[1]); privKeyFile = new File(args[2]); if (!privKeyFile.isAbsolute()) privKeyFile = new File(_context.getConfigDir(), args[2]); if (!privKeyFile.canRead()) { l.log(getPrefix() + "Private key file does not exist or is not readable: " + args[2]); _log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[2]); notifyEvent("serverTaskId", Integer.valueOf(-1)); throw new IllegalArgumentException(getPrefix() + "Cannot open private key file " + args[2]); } I2PTunnelServer serv = new I2PTunnelIRCServer(serverHost, portNum, privKeyFile, args[2], l, this, this); serv.setReadTimeout(readTimeout); serv.startRunning(); addtask(serv); notifyEvent("serverTaskId", Integer.valueOf(serv.getId())); return; } else { l.log("server <host> <port> <privkeyfile>\n" + " creates a server that sends all incoming data\n" + " of its destination to host:port."); notifyEvent("serverTaskId", Integer.valueOf(-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 * @throws IllegalArgumentException on config problem */ 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", Integer.valueOf(-1)); throw new IllegalArgumentException(getPrefix() + "Error resolving " + args[0] + uhe.getMessage()); } 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", Integer.valueOf(-1)); } if (portNum <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[1]); String spoofedHost = args[2]; privKeyFile = new File(args[3]); if (!privKeyFile.isAbsolute()) privKeyFile = new File(_context.getConfigDir(), args[3]); if (!privKeyFile.canRead()) { l.log(getPrefix() + "Private key file does not exist or is not readable: " + args[3]); _log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]); notifyEvent("serverTaskId", Integer.valueOf(-1)); throw new IllegalArgumentException(getPrefix() + "Cannot open private key file " + args[3]); } I2PTunnelHTTPServer serv = new I2PTunnelHTTPServer(serverHost, portNum, privKeyFile, args[3], spoofedHost, l, this, this); serv.setReadTimeout(readTimeout); serv.startRunning(); addtask(serv); notifyEvent("serverTaskId", Integer.valueOf(serv.getId())); return; } else { l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>\n" + " 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", Integer.valueOf(-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. Also runs an HTTP proxy for * bidirectional communications on the same tunnel destination.<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, proxyPortNumber, spoofedHost, privKeyFilename} * @param l logger to receive events and output * @throws IllegalArgumentException on config problem */ public void runHttpBidirServer(String args[], Logging l) { if (args.length == 5) { InetAddress serverHost = null; int portNum = -1; int port2Num = -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", Integer.valueOf(-1)); throw new IllegalArgumentException(getPrefix() + "Error resolving " + args[0] + uhe.getMessage()); } 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", Integer.valueOf(-1)); } try { port2Num = Integer.parseInt(args[2]); } catch (NumberFormatException nfe) { l.log("invalid port"); _log.error(getPrefix() + "Port specified is not valid: " + args[2], nfe); notifyEvent("serverTaskId", Integer.valueOf(-1)); } if (portNum <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[1]); if (port2Num <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[2]); String spoofedHost = args[3]; privKeyFile = new File(args[4]); if (!privKeyFile.isAbsolute()) privKeyFile = new File(_context.getConfigDir(), args[4]); if (!privKeyFile.canRead()) { l.log(getPrefix() + "Private key file does not exist or is not readable: " + args[4]); _log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[4]); notifyEvent("serverTaskId", Integer.valueOf(-1)); throw new IllegalArgumentException(getPrefix() + "Cannot open private key file " + args[4]); } I2PTunnelHTTPBidirServer serv = new I2PTunnelHTTPBidirServer(serverHost, portNum, port2Num, privKeyFile, args[3], spoofedHost, l, this, this); serv.setReadTimeout(readTimeout); serv.startRunning(); addtask(serv); notifyEvent("serverTaskId", Integer.valueOf(serv.getId())); return; } else { l.log("httpserver <host> <port> <proxyport> <spoofedhost> <privkeyfile>\n" + " creates a bidirectional 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," + " and listens to host:proxyport to proxy HTTP requests."); notifyEvent("serverTaskId", Integer.valueOf(-1)); } } /** * Run the server pointing at the host and port specified using the private i2p * destination loaded from the given base64 stream. <p> * * Deprecated? Why run a server with a private destination? * Not available from the war GUI * * 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 * @throws IllegalArgumentException on config problem */ 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", Integer.valueOf(-1)); throw new IllegalArgumentException(getPrefix() + "Error resolving " + args[0] + uhe.getMessage()); } 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", Integer.valueOf(-1)); } if (portNum <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[1]); I2PTunnelServer serv = new I2PTunnelServer(serverHost, portNum, args[2], l, this, this); serv.setReadTimeout(readTimeout); serv.startRunning(); addtask(serv); notifyEvent("serverTaskId", Integer.valueOf(serv.getId())); } else { l.log("textserver <host> <port> <privkey>\n" + " creates a server that sends all incoming data\n" + " of its destination to host:port."); notifyEvent("textserverTaskId", Integer.valueOf(-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 [, privKeyFile]]} * @param l logger to receive events and output * @throws IllegalArgumentException on config problem */ public void runClient(String args[], Logging l) { boolean isShared = true; if (args.length >= 3) isShared = Boolean.parseBoolean(args[2].trim()); if (args.length >= 2) { 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", Integer.valueOf(-1)); } if (portNum <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]); ownDest = !isShared; try { String privateKeyFile = null; if (args.length >= 4) privateKeyFile = args[3]; I2PTunnelClientBase task = new I2PTunnelClient(portNum, args[1], l, ownDest, this, this, privateKeyFile); task.startRunning(); addtask(task); notifyEvent("clientTaskId", Integer.valueOf(task.getId())); } catch (IllegalArgumentException iae) { String msg = "Invalid I2PTunnel configuration to create a standard client tunnel connecting to the router at " + host + ':'+ port + " and listening on " + listenHost + ':' + portNum; _log.error(getPrefix() + msg, iae); l.log(msg); notifyEvent("clientTaskId", Integer.valueOf(-1)); // Since nothing listens to TaskID events, use this to propagate the error to TunnelController // Otherwise, the tunnel stays up even though the port is down // This doesn't work for CLI though... and the tunnel doesn't close itself after error, // so this probably leaves the tunnel open if called from the CLI throw iae; } } else { l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>[ <sharedClient>] [<privKeyFile>]\n" + " Creates a standard client that listens on the port and forwards to the pubkey.\n" + " With a comma delimited list of pubkeys, it will rotate among them randomly.\n" + " sharedClient indicates if this client shares tunnels with other clients (true or false)"); notifyEvent("clientTaskId", Integer.valueOf(-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 * @throws IllegalArgumentException on config problem */ public void runHttpClient(String args[], Logging l) { if (args.length >= 1 && args.length <= 3) { int clientPort = -1; try { clientPort = Integer.parseInt(args[0]); } catch (NumberFormatException nfe) { l.log("invalid port"); _log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe); notifyEvent("httpclientTaskId", Integer.valueOf(-1)); } if (clientPort <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]); String proxy = ""; boolean isShared = true; if (args.length > 1) { if (Boolean.parseBoolean(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]; } } ownDest = !isShared; try { I2PTunnelClientBase task = new I2PTunnelHTTPClient(clientPort, l, ownDest, proxy, this, this); task.startRunning(); addtask(task); notifyEvent("httpclientTaskId", Integer.valueOf(task.getId())); } catch (IllegalArgumentException iae) { String msg = "Invalid I2PTunnel configuration to create an HTTP Proxy connecting to the router at " + host + ':'+ port + " and listening on " + listenHost + ':' + clientPort; _log.error(getPrefix() + msg, iae); l.log(msg); notifyEvent("httpclientTaskId", Integer.valueOf(-1)); // Since nothing listens to TaskID events, use this to propagate the error to TunnelController // Otherwise, the tunnel stays up even though the port is down // This doesn't work for CLI though... and the tunnel doesn't close itself after error, // so this probably leaves the tunnel open if called from the CLI throw iae; } } else { l.log("httpclient <port> [<sharedClient>] [<proxy>]\n" + " Creates a HTTP client proxy on the specified port.\n" + " <sharedClient> (optional) Indicates if this client shares tunnels with other clients (true or false)\n" + " <proxy> (optional) Indicates a proxy server to be used\n" + " when trying to access an address out of the .i2p domain"); notifyEvent("httpclientTaskId", Integer.valueOf(-1)); } } /** * Run a CONNECT client on the given port number * * @param args {portNumber[, sharedClient][, proxy to be used for the WWW]} * @param l logger to receive events and output * @throws IllegalArgumentException on config problem */ public void runConnectClient(String args[], Logging l) { if (args.length >= 1 && args.length <= 3) { int _port = -1; try { _port = Integer.parseInt(args[0]); } catch (NumberFormatException nfe) { _log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe); } if (_port <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]); String proxy = ""; boolean isShared = true; if (args.length > 1) { if (Boolean.parseBoolean(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]; } } ownDest = !isShared; try { I2PTunnelClientBase task = new I2PTunnelConnectClient(_port, l, ownDest, proxy, this, this); task.startRunning(); addtask(task); } catch (IllegalArgumentException iae) { String msg = "Invalid I2PTunnel configuration to create a CONNECT client connecting to the router at " + host + ':'+ port + " and listening on " + listenHost + ':' + _port; _log.error(getPrefix() + msg, iae); l.log(msg); // Since nothing listens to TaskID events, use this to propagate the error to TunnelController // Otherwise, the tunnel stays up even though the port is down // This doesn't work for CLI though... and the tunnel doesn't close itself after error, // so this probably leaves the tunnel open if called from the CLI throw iae; } } else { l.log("connectclient <port> [<sharedClient>] [<proxy>]\n" + " creates a client that for SSL/HTTPS requests.\n" + " <sharedClient> (optional) indicates if this client shares tunnels with other clients (true or false)\n" + " <proxy> (optional) indicates a proxy server to be used\n" + " when trying to access an address out of the .i2p domain\n"); } } /** * 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 [, privKeyFile]]} * @param l logger to receive events and output * @throws IllegalArgumentException on config problem */ public void runIrcClient(String args[], Logging l) { if (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("ircclientTaskId", Integer.valueOf(-1)); } if (_port <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]); boolean isShared = true; if (args.length > 2) { if (Boolean.parseBoolean(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; } } ownDest = !isShared; try { String privateKeyFile = null; if (args.length >= 4) privateKeyFile = args[3]; I2PTunnelClientBase task = new I2PTunnelIRCClient(_port, args[1], l, ownDest, this, this, privateKeyFile); task.startRunning(); addtask(task); notifyEvent("ircclientTaskId", Integer.valueOf(task.getId())); } catch (IllegalArgumentException iae) { String msg = "Invalid I2PTunnel configuration to create an IRC client connecting to the router at " + host + ':'+ port + " and listening on " + listenHost + ':' + _port; _log.error(getPrefix() + msg, iae); l.log(msg); notifyEvent("ircclientTaskId", Integer.valueOf(-1)); // Since nothing listens to TaskID events, use this to propagate the error to TunnelController // Otherwise, the tunnel stays up even though the port is down // This doesn't work for CLI though... and the tunnel doesn't close itself after error, // so this probably leaves the tunnel open if called from the CLI throw iae; } } else { l.log("ircclient <port> [<sharedClient> [<privKeyFile>]]\n" + " Creates an IRC client proxy on the specified port.\n" + " <sharedClient> (optional) Indicates if this client shares tunnels with other clients (true or false)\n"); notifyEvent("ircclientTaskId", Integer.valueOf(-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 [, sharedClient]} or (portNumber, ignored (false), privKeyFile) * @param l logger to receive events and output * @throws IllegalArgumentException on config problem */ public void runSOCKSTunnel(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("sockstunnelTaskId", Integer.valueOf(-1)); } if (_port <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]); boolean isShared = false; if (args.length == 2) isShared = Boolean.parseBoolean(args[1].trim()); ownDest = !isShared; String privateKeyFile = null; if (args.length == 3) privateKeyFile = args[2]; try { I2PTunnelClientBase task = new I2PSOCKSTunnel(_port, l, ownDest, this, this, privateKeyFile); task.startRunning(); addtask(task); notifyEvent("sockstunnelTaskId", Integer.valueOf(task.getId())); } catch (IllegalArgumentException iae) { String msg = "Invalid I2PTunnel configuration to create a SOCKS Proxy connecting to the router at " + host + ':'+ port + " and listening on " + listenHost + ':' + _port; _log.error(getPrefix() + msg, iae); l.log(msg); notifyEvent("sockstunnelTaskId", Integer.valueOf(-1)); throw iae; } } else { l.log("sockstunnel <port>\n" + " Creates a SOCKS proxy on the specified port."); notifyEvent("sockstunnelTaskId", Integer.valueOf(-1)); } } /** * Run an SOCKS IRC tunnel on the given port number * @param args {portNumber [, sharedClient]} or (portNumber, ignored (false), privKeyFile) * @throws IllegalArgumentException on config problem * @since 0.7.12 */ public void runSOCKSIRCTunnel(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("sockstunnelTaskId", Integer.valueOf(-1)); } if (_port <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]); boolean isShared = false; if (args.length == 2) isShared = Boolean.parseBoolean(args[1].trim()); ownDest = !isShared; String privateKeyFile = null; if (args.length == 3) privateKeyFile = args[2]; try { I2PTunnelClientBase task = new I2PSOCKSIRCTunnel(_port, l, ownDest, this, this, privateKeyFile); task.startRunning(); addtask(task); notifyEvent("sockstunnelTaskId", Integer.valueOf(task.getId())); } catch (IllegalArgumentException iae) { String msg = "Invalid I2PTunnel configuration to create a SOCKS IRC Proxy connecting to the router at " + host + ':'+ port + " and listening on " + listenHost + ':' + _port; _log.error(getPrefix() + msg, iae); l.log(msg); notifyEvent("sockstunnelTaskId", Integer.valueOf(-1)); throw iae; } } else { l.log("socksirctunnel <port> [<sharedClient> [<privKeyFile>]]\n" + " Creates a SOCKS IRC proxy on the specified port."); notifyEvent("sockstunnelTaskId", Integer.valueOf(-1)); } } /** * Streamr client * * @param args {targethost, targetport, destinationString} * @param l logger to receive events and output * @throws IllegalArgumentException on config problem */ public void runStreamrClient(String args[], Logging l) { if (args.length == 3) { InetAddress _host; try { _host = InetAddress.getByName(args[0]); } catch (UnknownHostException uhe) { l.log("unknown host"); _log.error(getPrefix() + "Error resolving " + args[0], uhe); notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1)); return; } int _port = -1; try { _port = Integer.parseInt(args[1]); } catch (NumberFormatException nfe) { l.log("invalid port"); _log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe); notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1)); } if (_port <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]); try { StreamrConsumer task = new StreamrConsumer(_host, _port, args[2], l, this, this); task.startRunning(); addtask(task); notifyEvent("streamrtunnelTaskId", Integer.valueOf(task.getId())); } catch (IllegalArgumentException iae) { String msg = "Invalid I2PTunnel configuration to create a Streamr Client connecting to the router at " + host + ':'+ port + " and sending to " + _host + ':' + _port; _log.error(getPrefix() + msg, iae); l.log(msg); notifyEvent("streamrtunnnelTaskId", Integer.valueOf(-1)); throw iae; } } else { l.log("streamrclient <host> <port> <destination>\n" + " creates a tunnel that receives streaming data."); notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1)); } } /** * Streamr server * * @param args {port, privkeyfile} * @param l logger to receive events and output * @throws IllegalArgumentException on config problem */ public void runStreamrServer(String args[], Logging l) { if (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("streamrtunnelTaskId", Integer.valueOf(-1)); } if (_port <= 0) throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]); File privKeyFile = new File(args[1]); if (!privKeyFile.isAbsolute()) privKeyFile = new File(_context.getConfigDir(), args[1]); 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", Integer.valueOf(-1)); return; } StreamrProducer task = new StreamrProducer(_port, privKeyFile, args[1], l, this, this); task.startRunning(); addtask(task); notifyEvent("streamrtunnelTaskId", Integer.valueOf(task.getId())); } else { l.log("streamrserver <port> <privkeyfile>\n" + " creates a tunnel that sends streaming data."); notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1)); } } /** * Specify the i2cp host and port * Deprecated - only used by CLI * * 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 */ private void runConfig(String args[], Logging l) { if (args.length >= 1) { int i = 0; boolean ssl = args[0].equals("-s"); if (ssl) { _clientOptions.setProperty("i2cp.SSL", "true"); i++; } else { _clientOptions.remove("i2cp.SSL"); } if (i < args.length) { host = args[i++]; } if (i < args.length) port = args[i]; l.log("New setting: " + host + ' ' + port + (ssl ? " SSL" : " non-SSL")); notifyEvent("configResult", "ok"); } else { boolean ssl = Boolean.parseBoolean(_clientOptions.getProperty("i2cp.SSL")); l.log("Usage:\n" + " config [-s] [<i2phost>] [<i2pport>]\n" + " Sets the address and port of the I2P router.\n" + " Use -s for SSL.\n" + "Current setting: " + host + ' ' + port + (ssl ? " SSL" : " non-SSL")); notifyEvent("configResult", "error"); } } /** * Specify the i2cp username and password * * @param args {username, password} * @param l logger to receive events and output * @since 0.9.11 */ private void runAuth(String args[], Logging l) { if (args.length == 2) { _clientOptions.setProperty("i2cp.username", args[0]); _clientOptions.setProperty("i2cp.password", args[1]); } else { l.log("Usage:\n" + " auth <username> <password>\n" + " Sets the i2cp credentials"); } } /** * Specify whether to use its own destination for each outgoing tunnel * Deprecated - only used by CLI * * 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 */ private 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\n" + " 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>\n" + " 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>\n" + " 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. * Does NOT support non-default sig types. * Deprecated - only used by CLI * * 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 */ private static 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>]\n" + " 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); } finally { if(pubdest != null) try { pubdest.close(); } catch(IOException ioe) {} } } /** * Generate a new keypair. * Does NOT support non-default sig types. * Deprecated - only used by CLI * * 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 */ private static void runGenTextKeys(Logging l) { ByteArrayOutputStream privkey = new ByteArrayOutputStream(1024); 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. * Deprecated - only used by CLI * * 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 */ private void runQuit(Logging l) { purgetasks(l); if (tasks.isEmpty()) { System.exit(0); } l.log("There are running tasks. Try 'list' or 'close all'."); //notifyEvent("quitResult", "error"); } /** * Retrieve a list of currently running tasks * Deprecated - only used by CLI * * Sets the event "listDone" = "done" after dumping the tasks to * the logger * * @param l logger to receive events and output */ private void runList(Logging l) { purgetasks(l); for (I2PTunnelTask t : tasks) { 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"}, {"destroy", jobNumber}, {"destroy", "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|destroy] <jobnumber>|all\n" + " 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 CloseMode mode = CloseMode.NORMAL; if (args[argindex].equalsIgnoreCase("forced")) { mode = CloseMode.FORCED; argindex++; } else if (args[argindex].equalsIgnoreCase("destroy")) { mode = CloseMode.DESTROY; argindex++; } if (args[argindex].equalsIgnoreCase("all")) { boolean error = false; if (tasks.isEmpty()) { if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + " runClose(all) no tasks"); } for (I2PTunnelTask t : tasks) { if (!closetask(t, mode, 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]), mode, 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) * Deprecated - only used by CLI * * Sets the event "runResult" = "ok" or "error" after the closing is complete * * @param args {filename} * @param l logger to receive events and output */ private void runRun(String args[], Logging l) { if (args.length == 1) { try { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(args[0]), "UTF-8")); 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>\n" + " 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 * Deprecated - only used by CLI * * Sets the event "lookupResult" = base64 of the destination, or an error message * * @param args {name} * @param l logger to receive events and output */ private void runLookup(String args[], Logging l) { if (args.length != 1) { l.log("lookup <name>\n" + " try to resolve the name into a destination key"); notifyEvent("lookupResult", "invalidUsage"); } else { try { boolean ssl = Boolean.parseBoolean(_clientOptions.getProperty("i2cp.SSL")); String user = _clientOptions.getProperty("i2cp.username"); String pw = _clientOptions.getProperty("i2cp.password"); Destination dest = destFromName(args[0], host, port, ssl, user, pw); if (dest == null) { l.log("Unknown host: " + args[0]); notifyEvent("lookupResult", "unkown host"); } else { l.log(dest.toBase64()); notifyEvent("lookupResult", dest.toBase64()); } } catch (DataFormatException dfe) { l.log("Unknown or invalid host: " + args[0]); notifyEvent("lookupResult", "invalid host"); } } } /** * Start up a ping task with the specified args (currently supporting -ns, -h, -l) * Deprecated - only used by CLI * * 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 */ private void runPing(String allargs, Logging l) { if (allargs.length() != 0) { _clientOptions.setProperty(I2Ping.PROP_COMMAND, allargs); if (ownDest) { if (!_clientOptions.containsKey("inbound.nickname")) _clientOptions.setProperty("inbound.nickname", "I2Ping"); if (!_clientOptions.containsKey("outbound.nickname")) _clientOptions.setProperty("outbound.nickname", "I2Ping"); } I2PTunnelClientBase task = new I2Ping(l, ownDest, this, this); task.startRunning(); addtask(task); notifyEvent("pingTaskId", Integer.valueOf(task.getId())); } else { l.log(I2Ping.usage()); notifyEvent("pingTaskId", Integer.valueOf(-1)); } } /** * Helper method to actually close the given task number (optionally forcing * closure) * */ private boolean closetask(int num, CloseMode mode, Logging l) { boolean closed = false; _log.debug(getPrefix() + "closetask(): looking for task " + num); for (I2PTunnelTask t : tasks) { int id = t.getId(); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "closetask(): parsing task " + id + " (" + t.toString() + ")"); if (id == num) { closed = closetask(t, mode, 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, CloseMode mode, Logging l) { if (_log.shouldLog(Log.INFO)) _log.info("Closing task " + t.getId() + " mode: " + mode); //l.log("Closing task " + t.getId() + (forced ? " forced..." : "...")); boolean success; if (mode == CloseMode.NORMAL) success = t.close(false); else if (mode == CloseMode.FORCED) success = t.close(true); else // DESTROY success = t.destroy(); if (success) { if (_log.shouldLog(Log.INFO)) _log.info("Task " + t.getId() + " closed."); //l.log("Task " + t.getId() + " closed."); } return success; } /** * Helper task to remove closed / completed tasks. * */ private void purgetasks(Logging l) { List<I2PTunnelTask> removed = new ArrayList<I2PTunnelTask>(); for (I2PTunnelTask t : tasks) { if (!t.isOpen()) { _log.debug(getPrefix() + "Purging inactive tunnel: [" + t.getId() + "] " + t.toString()); removed.add(t); } } tasks.removeAll(removed); } /** * Log the given message (using both the logging subsystem and standard output...) * */ public void log(String s) { System.out.println(s); //if (_log.shouldLog(Log.INFO)) // _log.info(getPrefix() + "Display: " + s); } /** * Create a new destination, storing the destination and its private keys where * instructed. * Does NOT support non-default sig types. * Deprecated - only used by CLI * * @param writeTo location to store the destination and private keys * @param pubDest location to store the destination * @param l logger to send messages to */ private static void makeKey(OutputStream writeTo, OutputStream pubDest, Logging l) { try { l.log("Generating new keys..."); I2PClient client = I2PClientFactory.createClient(); Destination d = client.createDestination(writeTo); l.log("New destination: " + d.toBase32()); 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 * Deprecated - only used by CLI * * @param readFrom stream to read the destination from * @param pubDest stream to write the destination to * @param l logger to send messages to */ private static void showKey(InputStream readFrom, OutputStream pubDest, Logging l) { try { Destination d = new Destination(); d.readBytes(readFrom); l.log("Destination: " + d.toBase32()); readFrom.close(); writePubKey(d, pubDest, l); } catch (I2PException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } /** * Write out the destination to the stream * Deprecated - only used by CLI * * @param d Destination to write * @param o stream to write the destination to, or null for noop * @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. * * Since file:<filename> isn't really used, this method is deprecated, * just call context.namingService.lookup() directly. * @deprecated Don't use i2ptunnel for lookup! Use I2PAppContext.getGlobalContext().namingService().lookup(name) from i2p.jar */ @Deprecated public static Destination destFromName(String name) throws DataFormatException { return destFromName(name, null, null, false, null, null); } /** * @param i2cpHost may be null * @param i2cpPort may be null * @param user may be null * @param pw may be null * @since 0.9.11 */ private static Destination destFromName(String name, String i2cpHost, String i2cpPort, boolean isSSL, String user, String pw) 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 (RuntimeException 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 name = name.trim(); NamingService inst = ctx.namingService(); boolean b32 = name.length() == 60 && name.toLowerCase(Locale.US).endsWith(".b32.i2p"); Destination d = null; if (ctx.isRouterContext() || !b32) { // Local lookup. // Even though we could do b32 outside router ctx here, // we do it below instead so we can set the host and port, // which we can't do with lookup() d = inst.lookup(name); if (d != null || ctx.isRouterContext() || name.length() >= 516) return d; } // Outside router context only, // try simple session to ask the router. I2PClient client = new I2PSimpleClient(); Properties opts = new Properties(); if (i2cpHost != null) opts.put(I2PClient.PROP_TCP_HOST, i2cpHost); if (i2cpPort != null) opts.put(I2PClient.PROP_TCP_PORT, i2cpPort); opts.put("i2cp.SSL", Boolean.toString(isSSL)); if (user != null) opts.put("i2cp.username", user); if (pw != null) opts.put("i2cp.password", pw); I2PSession session = null; try { session = client.createSession(null, opts); session.connect(); d = session.lookupDest(name); } catch (I2PSessionException ise) { if (log.shouldLog(Log.WARN)) log.warn("Lookup via router failed", ise); } finally { if (session != null) { try { session.destroySession(); } catch (I2PSessionException ise) {} } } return d; } } public void addConnectionEventListener(ConnectionEventListener lsnr) { if (lsnr == null) return; listeners.add(lsnr); } public void removeConnectionEventListener(ConnectionEventListener lsnr) { if (lsnr == null) return; 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"); for (ConnectionEventListener lsnr : listeners) { if (lsnr != null) lsnr.routerDisconnected(); } } /** * Callback routine to find out */ public interface ConnectionEventListener { public void routerDisconnected(); } }