From 233cce8311b66f9bb6004adbccf93d6d55da80c8 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 16 Jun 2015 13:59:27 +0000 Subject: [PATCH 01/46] remove _args field --- apps/sam/java/src/net/i2p/sam/SAMBridge.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMBridge.java b/apps/sam/java/src/net/i2p/sam/SAMBridge.java index 383f44fb5..6e0f8e7e5 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMBridge.java +++ b/apps/sam/java/src/net/i2p/sam/SAMBridge.java @@ -65,7 +65,6 @@ public class SAMBridge implements Runnable, ClientApp { private volatile boolean acceptConnections = true; private final ClientAppManager _mgr; - private final String[] _args; private volatile ClientAppState _state = UNINITIALIZED; private static final int SAM_LISTENPORT = 7656; @@ -95,7 +94,6 @@ public class SAMBridge implements Runnable, ClientApp { public SAMBridge(I2PAppContext context, ClientAppManager mgr, String[] args) throws Exception { _log = context.logManager().getLog(SAMBridge.class); _mgr = mgr; - _args = args; Options options = getOptions(args); _listenHost = options.host; _listenPort = options.port; @@ -126,7 +124,6 @@ public class SAMBridge implements Runnable, ClientApp { public SAMBridge(String listenHost, int listenPort, Properties i2cpProps, String persistFile) { _log = I2PAppContext.getGlobalContext().logManager().getLog(SAMBridge.class); _mgr = null; - _args = new String[] {listenHost, Integer.toString(listenPort) }; // placeholder _listenHost = listenHost; _listenPort = listenPort; persistFilename = persistFile; @@ -381,7 +378,7 @@ public class SAMBridge implements Runnable, ClientApp { * @since 0.9.6 */ public String getDisplayName() { - return "SAM " + Arrays.toString(_args); + return "SAM " + _listenHost + ':' + _listenPort; } ////// end ClientApp interface From 5878fae88f2997d4a83ac9d7a1bd9d2429255a8c Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 17 Jun 2015 02:22:28 +0000 Subject: [PATCH 02/46] Use getopt for SAM args processing Args processing cleanups Change default host from 0.0.0.0 to 127.0.0.1 Add -s option for SSL (unimplemented) Put help text in a single string --- apps/sam/java/src/net/i2p/sam/SAMBridge.java | 142 +++++++++++++------ 1 file changed, 97 insertions(+), 45 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMBridge.java b/apps/sam/java/src/net/i2p/sam/SAMBridge.java index 6e0f8e7e5..5f03436c2 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMBridge.java +++ b/apps/sam/java/src/net/i2p/sam/SAMBridge.java @@ -27,6 +27,8 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import gnu.getopt.Getopt; + import net.i2p.I2PAppContext; import net.i2p.app.*; import static net.i2p.app.ClientAppState.*; @@ -72,12 +74,12 @@ public class SAMBridge implements Runnable, ClientApp { public static final String DEFAULT_SAM_KEYFILE = "sam.keys"; public static final String PROP_TCP_HOST = "sam.tcp.host"; public static final String PROP_TCP_PORT = "sam.tcp.port"; - protected static final String DEFAULT_TCP_HOST = "0.0.0.0"; + protected static final String DEFAULT_TCP_HOST = "127.0.0.1"; protected static final String DEFAULT_TCP_PORT = "7656"; public static final String PROP_DATAGRAM_HOST = "sam.udp.host"; public static final String PROP_DATAGRAM_PORT = "sam.udp.port"; - protected static final String DEFAULT_DATAGRAM_HOST = "0.0.0.0"; + protected static final String DEFAULT_DATAGRAM_HOST = "127.0.0.1"; protected static final String DEFAULT_DATAGRAM_PORT = "7655"; @@ -456,9 +458,11 @@ public class SAMBridge implements Runnable, ClientApp { private final String host, keyFile; private final int port; private final Properties opts; + private final boolean isSSL; - public Options(String host, int port, Properties opts, String keyFile) { + public Options(String host, int port, boolean isSSL, Properties opts, String keyFile) { this.host = host; this.port = port; this.opts = opts; this.keyFile = keyFile; + this.isSSL = isSSL; } } @@ -479,67 +483,115 @@ public class SAMBridge implements Runnable, ClientApp { String keyfile = DEFAULT_SAM_KEYFILE; int port = SAM_LISTENPORT; String host = DEFAULT_TCP_HOST; + boolean isSSL = false; Properties opts = null; - if (args.length > 0) { - opts = parseOptions(args, 0); - keyfile = args[0]; - int portIndex = 1; - try { - if (args.length>portIndex) port = Integer.parseInt(args[portIndex]); - } catch (NumberFormatException nfe) { - host = args[portIndex]; - portIndex++; - try { - if (args.length>portIndex) port = Integer.parseInt(args[portIndex]); - } catch (NumberFormatException nfe1) { - port = Integer.parseInt(opts.getProperty(SAMBridge.PROP_TCP_PORT, SAMBridge.DEFAULT_TCP_PORT)); - host = opts.getProperty(SAMBridge.PROP_TCP_HOST, SAMBridge.DEFAULT_TCP_HOST); - } - } + Getopt g = new Getopt("SAM", args, "hs"); + int c; + while ((c = g.getopt()) != -1) { + switch (c) { + case 's': + isSSL = true; + break; + + case 'h': + case '?': + case ':': + default: + throw new HelpRequestedException(); + } // switch + } // while + + int startArgs = g.getOptind(); + // possible args before ones containing '='; + // (none) + // key port + // key host port + int startOpts; + for (startOpts = startArgs; startOpts < args.length; startOpts++) { + if (args[startOpts].contains("=")) + break; } - return new Options(host, port, opts, keyfile); + int numArgs = startOpts - startArgs; + switch (numArgs) { + case 0: + break; + + case 2: + keyfile = args[startArgs]; + try { + port = Integer.parseInt(args[startArgs + 1]); + } catch (NumberFormatException nfe) { + throw new HelpRequestedException(); + } + break; + + case 3: + keyfile = args[startArgs]; + host = args[startArgs + 1]; + try { + port = Integer.parseInt(args[startArgs + 2]); + } catch (NumberFormatException nfe) { + throw new HelpRequestedException(); + } + break; + + default: + throw new HelpRequestedException(); + } + + int remaining = args.length - startOpts; + if (remaining > 0) { + opts = parseOptions(args, startOpts); + } + return new Options(host, port, isSSL, opts, keyfile); } + /** + * Parse key=value options starting at startArgs. + * @throws HelpRequestedException on any item not of the form key=value. + */ private static Properties parseOptions(String args[], int startArgs) throws HelpRequestedException { Properties props = new Properties(); - // skip over first few options for (int i = startArgs; i < args.length; i++) { - if (args[i].equals("-h")) throw new HelpRequestedException(); int eq = args[i].indexOf('='); - if (eq <= 0) continue; - if (eq >= args[i].length()-1) continue; + if (eq <= 0) + throw new HelpRequestedException(); + if (eq >= args[i].length()-1) + throw new HelpRequestedException(); String key = args[i].substring(0, eq); String val = args[i].substring(eq+1); key = key.trim(); val = val.trim(); if ( (key.length() > 0) && (val.length() > 0) ) props.setProperty(key, val); + else + throw new HelpRequestedException(); } return props; } private static void usage() { - System.err.println("Usage: SAMBridge [keyfile [listenHost] listenPortNum[ name=val]*]"); - System.err.println("or:"); - System.err.println(" SAMBridge [ name=val ]*"); - System.err.println(" keyfile: location to persist private keys (default sam.keys)"); - System.err.println(" listenHost: interface to listen on (0.0.0.0 for all interfaces)"); - System.err.println(" listenPort: port to listen for SAM connections on (default 7656)"); - System.err.println(" name=val: options to pass when connecting via I2CP, such as "); - System.err.println(" i2cp.host=localhost and i2cp.port=7654"); - System.err.println(""); - System.err.println("Host and ports of the SAM bridge can be specified with the alternate"); - System.err.println("form by specifying options "+SAMBridge.PROP_TCP_HOST+" and/or "+ - SAMBridge.PROP_TCP_PORT); - System.err.println(""); - System.err.println("Options "+SAMBridge.PROP_DATAGRAM_HOST+" and "+SAMBridge.PROP_DATAGRAM_PORT+ - " specify the listening ip"); - System.err.println("range and the port of SAM datagram server. This server is"); - System.err.println("only launched after a client creates the first SAM datagram"); - System.err.println("or raw session, after a handshake with SAM version >= 3.0."); - System.err.println(""); - System.err.println("The option loglevel=[DEBUG|WARN|ERROR|CRIT] can be used"); - System.err.println("for tuning the log verbosity.\n"); + System.err.println("Usage: SAMBridge [keyfile [listenHost] listenPortNum[ name=val]*]\n" + + "or:\n" + + " SAMBridge [ name=val ]*\n" + + " keyfile: location to persist private keys (default sam.keys)\n" + + " listenHost: interface to listen on (0.0.0.0 for all interfaces)\n" + + " listenPort: port to listen for SAM connections on (default 7656)\n" + + " name=val: options to pass when connecting via I2CP, such as \n" + + " i2cp.host=localhost and i2cp.port=7654\n" + + "\n" + + "Host and ports of the SAM bridge can be specified with the alternate\n" + + "form by specifying options "+SAMBridge.PROP_TCP_HOST+" and/or "+ + SAMBridge.PROP_TCP_PORT + + "\n" + + "Options "+SAMBridge.PROP_DATAGRAM_HOST+" and "+SAMBridge.PROP_DATAGRAM_PORT+ + " specify the listening ip\n" + + "range and the port of SAM datagram server. This server is\n" + + "only launched after a client creates the first SAM datagram\n" + + "or raw session, after a handshake with SAM version >= 3.0.\n" + + "\n" + + "The option loglevel=[DEBUG|WARN|ERROR|CRIT] can be used\n" + + "for tuning the log verbosity."); } public void run() { From b6cb074c043a4b883eb1f1a169a8e8dcb880b929 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 26 Jun 2015 15:40:20 +0000 Subject: [PATCH 03/46] Add sam.config file support and -c file option Add partial SSL support (will require Java 7 due to SocketChannel changes) won't compile, SSLServerSocketChannel and SSLSocketChannel not checked in, pending decisions on implementation Bump version to 3.2 --- apps/sam/java/src/net/i2p/sam/SAMBridge.java | 115 ++++++++--- .../src/net/i2p/sam/SAMHandlerFactory.java | 5 +- apps/sam/java/src/net/i2p/sam/SSLUtil.java | 188 ++++++++++++++++++ 3 files changed, 284 insertions(+), 24 deletions(-) create mode 100644 apps/sam/java/src/net/i2p/sam/SSLUtil.java diff --git a/apps/sam/java/src/net/i2p/sam/SAMBridge.java b/apps/sam/java/src/net/i2p/sam/SAMBridge.java index 5f03436c2..77ec2cffe 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMBridge.java +++ b/apps/sam/java/src/net/i2p/sam/SAMBridge.java @@ -9,15 +9,16 @@ package net.i2p.sam; */ import java.io.BufferedReader; +import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; -import java.nio.ByteBuffer; import java.util.Arrays; import java.util.ArrayList; import java.util.HashMap; @@ -27,16 +28,22 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; + import gnu.getopt.Getopt; import net.i2p.I2PAppContext; import net.i2p.app.*; import static net.i2p.app.ClientAppState.*; import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.util.I2PAppThread; +import net.i2p.util.I2PSSLSocketFactory; import net.i2p.util.Log; import net.i2p.util.PortMapper; +import net.i2p.util.SystemVersion; /** * SAM bridge implementation. @@ -50,6 +57,7 @@ public class SAMBridge implements Runnable, ClientApp { private final String _listenHost; private final int _listenPort; private final Properties i2cpProps; + private final boolean _useSSL; private volatile Thread _runner; /** @@ -72,6 +80,9 @@ public class SAMBridge implements Runnable, ClientApp { private static final int SAM_LISTENPORT = 7656; public static final String DEFAULT_SAM_KEYFILE = "sam.keys"; + static final String DEFAULT_SAM_CONFIGFILE = "sam.config"; + private static final String PROP_SAM_KEYFILE = "sam.keyfile"; + private static final String PROP_SAM_SSL = "sam.useSSL"; public static final String PROP_TCP_HOST = "sam.tcp.host"; public static final String PROP_TCP_PORT = "sam.tcp.port"; protected static final String DEFAULT_TCP_HOST = "127.0.0.1"; @@ -99,6 +110,9 @@ public class SAMBridge implements Runnable, ClientApp { Options options = getOptions(args); _listenHost = options.host; _listenPort = options.port; + _useSSL = options.isSSL; + if (_useSSL && !SystemVersion.isJava7()) + throw new IllegalArgumentException("SSL requires Java 7 or higher"); persistFilename = options.keyFile; nameToPrivKeys = new HashMap(8); _handlers = new HashSet(8); @@ -123,11 +137,15 @@ public class SAMBridge implements Runnable, ClientApp { * @param persistFile location to store/load named keys to/from * @throws RuntimeException if a server socket can't be opened */ - public SAMBridge(String listenHost, int listenPort, Properties i2cpProps, String persistFile) { + public SAMBridge(String listenHost, int listenPort, boolean isSSL, Properties i2cpProps, String persistFile) { _log = I2PAppContext.getGlobalContext().logManager().getLog(SAMBridge.class); _mgr = null; _listenHost = listenHost; _listenPort = listenPort; + _useSSL = isSSL; + if (_useSSL && !SystemVersion.isJava7()) + throw new IllegalArgumentException("SSL requires Java 7 or higher"); + this.i2cpProps = i2cpProps; persistFilename = persistFile; nameToPrivKeys = new HashMap(8); _handlers = new HashSet(8); @@ -141,7 +159,6 @@ public class SAMBridge implements Runnable, ClientApp { + ":" + listenPort, e); throw new RuntimeException(e); } - this.i2cpProps = i2cpProps; _state = INITIALIZED; } @@ -149,17 +166,28 @@ public class SAMBridge implements Runnable, ClientApp { * @since 0.9.6 */ private void openSocket() throws IOException { - if ( (_listenHost != null) && !("0.0.0.0".equals(_listenHost)) ) { - serverSocket = ServerSocketChannel.open(); - serverSocket.socket().bind(new InetSocketAddress(_listenHost, _listenPort)); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("SAM bridge listening on " - + _listenHost + ":" + _listenPort); + if (_useSSL) { + SSLServerSocketFactory fact = SSLUtil.initializeFactory(i2cpProps); + InetAddress addr; + if (_listenHost != null && !_listenHost.equals("0.0.0.0")) + addr = InetAddress.getByName(_listenHost); + else + addr = null; + SSLServerSocket sock = (SSLServerSocket) fact.createServerSocket(_listenPort, 0, addr); + I2PSSLSocketFactory.setProtocolsAndCiphers(sock); + serverSocket = new SSLServerSocketChannel(sock); } else { serverSocket = ServerSocketChannel.open(); - serverSocket.socket().bind(new InetSocketAddress(_listenPort)); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("SAM bridge listening on 0.0.0.0:" + _listenPort); + if (_listenHost != null && !_listenHost.equals("0.0.0.0")) { + serverSocket.socket().bind(new InetSocketAddress(_listenHost, _listenPort)); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("SAM bridge listening on " + + _listenHost + ":" + _listenPort); + } else { + serverSocket.socket().bind(new InetSocketAddress(_listenPort)); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("SAM bridge listening on 0.0.0.0:" + _listenPort); + } } } @@ -420,7 +448,7 @@ public class SAMBridge implements Runnable, ClientApp { public static void main(String args[]) { try { Options options = getOptions(args); - SAMBridge bridge = new SAMBridge(options.host, options.port, options.opts, options.keyFile); + SAMBridge bridge = new SAMBridge(options.host, options.port, options.isSSL, options.opts, options.keyFile); bridge.startThread(); } catch (RuntimeException e) { e.printStackTrace(); @@ -477,15 +505,18 @@ public class SAMBridge implements Runnable, ClientApp { * depth, etc. * @param args [ keyfile [ listenHost ] listenPort [ name=val ]* ] * @return non-null Options or throws Exception + * @throws HelpRequestedException on command line problems + * @throws IllegalArgumentException if specified config file does not exist + * @throws IOException if specified config file cannot be read, or on SSL keystore problems * @since 0.9.6 */ private static Options getOptions(String args[]) throws Exception { - String keyfile = DEFAULT_SAM_KEYFILE; - int port = SAM_LISTENPORT; - String host = DEFAULT_TCP_HOST; + String keyfile = null; + int port = -1; + String host = null; boolean isSSL = false; - Properties opts = null; - Getopt g = new Getopt("SAM", args, "hs"); + String cfile = null; + Getopt g = new Getopt("SAM", args, "hsc:"); int c; while ((c = g.getopt()) != -1) { switch (c) { @@ -493,6 +524,10 @@ public class SAMBridge implements Runnable, ClientApp { isSSL = true; break; + case 'c': + cfile = g.getOptarg(); + break; + case 'h': case '?': case ':': @@ -539,19 +574,52 @@ public class SAMBridge implements Runnable, ClientApp { throw new HelpRequestedException(); } + String scfile = cfile != null ? cfile : DEFAULT_SAM_CONFIGFILE; + File file = new File(scfile); + if (!file.isAbsolute()) + file = new File(I2PAppContext.getGlobalContext().getConfigDir(), scfile); + + Properties opts = new Properties(); + if (file.exists()) { + DataHelper.loadProps(opts, file); + } else if (cfile != null) { + // only throw if specified on command line + throw new IllegalArgumentException("Config file not found: " + file); + } + // command line trumps config file trumps defaults + if (host == null) + host = opts.getProperty(PROP_TCP_HOST, DEFAULT_TCP_HOST); + if (port < 0) { + try { + port = Integer.parseInt(opts.getProperty(PROP_TCP_PORT, DEFAULT_TCP_PORT)); + } catch (NumberFormatException nfe) { + throw new HelpRequestedException(); + } + } + if (keyfile == null) + keyfile = opts.getProperty(PROP_SAM_KEYFILE, DEFAULT_SAM_KEYFILE); + if (!isSSL) + isSSL = Boolean.parseBoolean(opts.getProperty(PROP_SAM_SSL)); + if (isSSL) { + // must do this before we add command line opts since we may be writing them back out + boolean shouldSave = SSLUtil.verifyKeyStore(opts); + if (shouldSave) + DataHelper.storeProps(opts, file); + } + int remaining = args.length - startOpts; if (remaining > 0) { - opts = parseOptions(args, startOpts); + parseOptions(args, startOpts, opts); } return new Options(host, port, isSSL, opts, keyfile); } /** * Parse key=value options starting at startArgs. + * @param props out parameter, any options found are added * @throws HelpRequestedException on any item not of the form key=value. */ - private static Properties parseOptions(String args[], int startArgs) throws HelpRequestedException { - Properties props = new Properties(); + private static void parseOptions(String args[], int startArgs, Properties props) throws HelpRequestedException { for (int i = startArgs; i < args.length; i++) { int eq = args[i].indexOf('='); if (eq <= 0) @@ -567,13 +635,14 @@ public class SAMBridge implements Runnable, ClientApp { else throw new HelpRequestedException(); } - return props; } private static void usage() { - System.err.println("Usage: SAMBridge [keyfile [listenHost] listenPortNum[ name=val]*]\n" + + System.err.println("Usage: SAMBridge [-s] [-c sam.config] [keyfile [listenHost] listenPortNum[ name=val]*]\n" + "or:\n" + " SAMBridge [ name=val ]*\n" + + " -s: Use SSL\n" + + " -c sam.config: Specify config file\n" + " keyfile: location to persist private keys (default sam.keys)\n" + " listenHost: interface to listen on (0.0.0.0 for all interfaces)\n" + " listenPort: port to listen for SAM connections on (default 7656)\n" + diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java index 582854d87..dc4c8f24f 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java +++ b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java @@ -25,7 +25,7 @@ import net.i2p.util.VersionComparator; */ class SAMHandlerFactory { - private static final String VERSION = "3.1"; + private static final String VERSION = "3.2"; private static final int HELLO_TIMEOUT = 60*1000; @@ -131,6 +131,9 @@ class SAMHandlerFactory { if (VersionComparator.comp(VERSION, minVer) >= 0 && VersionComparator.comp(VERSION, maxVer) <= 0) return VERSION; + if (VersionComparator.comp("3.1", minVer) >= 0 && + VersionComparator.comp("3.1", maxVer) <= 0) + return "3.1"; // in VersionComparator, "3" < "3.0" so // use comparisons carefully if (VersionComparator.comp("3.0", minVer) >= 0 && diff --git a/apps/sam/java/src/net/i2p/sam/SSLUtil.java b/apps/sam/java/src/net/i2p/sam/SSLUtil.java new file mode 100644 index 000000000..bc0331417 --- /dev/null +++ b/apps/sam/java/src/net/i2p/sam/SSLUtil.java @@ -0,0 +1,188 @@ +package net.i2p.sam; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.GeneralSecurityException; +import java.util.Properties; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLContext; + +import net.i2p.I2PAppContext; +import net.i2p.crypto.KeyStoreUtil; +import net.i2p.util.Log; +import net.i2p.util.SecureDirectory; + +/** + * Utilities for SAM SSL server sockets. + * + * @since 0.9.22 adopted from net.i2p.i2ptunnel.SSLClientUtil + */ +class SSLUtil { + + private static final String PROP_KEYSTORE_PASSWORD = "sam.keystorePassword"; + private static final String DEFAULT_KEYSTORE_PASSWORD = "changeit"; + private static final String PROP_KEY_PASSWORD = "sam.keyPassword"; + private static final String PROP_KEY_ALIAS = "sam.keyAlias"; + private static final String ASCII_KEYFILE_SUFFIX = ".local.crt"; + private static final String PROP_KS_NAME = "sam.keystoreFile"; + private static final String KS_DIR = "keystore"; + private static final String PREFIX = "sam-"; + private static final String KS_SUFFIX = ".ks"; + private static final String CERT_DIR = "certificates/sam"; + + /** + * Create a new selfsigned cert and keystore and pubkey cert if they don't exist. + * May take a while. + * + * @param opts in/out, updated if rv is true + * @return false if it already exists; if true, caller must save opts + * @throws IOException on creation fail + */ + public static boolean verifyKeyStore(Properties opts) throws IOException { + String name = opts.getProperty(PROP_KEY_ALIAS); + if (name == null) { + name = KeyStoreUtil.randomString(); + opts.setProperty(PROP_KEY_ALIAS, name); + } + String ksname = opts.getProperty(PROP_KS_NAME); + if (ksname == null) { + ksname = PREFIX + name + KS_SUFFIX; + opts.setProperty(PROP_KS_NAME, ksname); + } + File ks = new File(ksname); + if (!ks.isAbsolute()) { + ks = new File(I2PAppContext.getGlobalContext().getConfigDir(), KS_DIR); + ks = new File(ks, ksname); + } + if (ks.exists()) + return false; + File dir = ks.getParentFile(); + if (!dir.exists()) { + File sdir = new SecureDirectory(dir.getAbsolutePath()); + if (!sdir.mkdirs()) + throw new IOException("Unable to create keystore " + ks); + } + boolean rv = createKeyStore(ks, name, opts); + if (!rv) + throw new IOException("Unable to create keystore " + ks); + + // Now read it back out of the new keystore and save it in ascii form + // where the clients can get to it. + // Failure of this part is not fatal. + exportCert(ks, name, opts); + return true; + } + + + /** + * Call out to keytool to create a new keystore with a keypair in it. + * + * @param name used in CNAME + * @param opts in/out, updated if rv is true, must contain PROP_KEY_ALIAS + * @return success, if true, opts will have password properties added to be saved + */ + private static boolean createKeyStore(File ks, String name, Properties opts) { + // make a random 48 character password (30 * 8 / 5) + String keyPassword = KeyStoreUtil.randomString(); + // and one for the cname + String cname = name + ".sam.i2p.net"; + + String keyName = opts.getProperty(PROP_KEY_ALIAS); + boolean success = KeyStoreUtil.createKeys(ks, keyName, cname, "SAM", keyPassword); + if (success) { + success = ks.exists(); + if (success) { + opts.setProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD); + opts.setProperty(PROP_KEY_PASSWORD, keyPassword); + } + } + if (success) { + logAlways("Created self-signed certificate for " + cname + " in keystore: " + ks.getAbsolutePath() + "\n" + + "The certificate name was generated randomly, and is not associated with your " + + "IP address, host name, router identity, or destination keys."); + } else { + error("Failed to create SAM SSL keystore.\n" + + "If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD + + " to " + (new File(I2PAppContext.getGlobalContext().getConfigDir(), SAMBridge.DEFAULT_SAM_CONFIGFILE)).getAbsolutePath()); + } + return success; + } + + /** + * Pull the cert back OUT of the keystore and save it as ascii + * so the clients can get to it. + * + * @param name used to generate output file name + * @param opts must contain PROP_KEY_ALIAS + */ + private static void exportCert(File ks, String name, Properties opts) { + File sdir = new SecureDirectory(I2PAppContext.getGlobalContext().getConfigDir(), CERT_DIR); + if (sdir.exists() || sdir.mkdirs()) { + String keyAlias = opts.getProperty(PROP_KEY_ALIAS); + String ksPass = opts.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD); + File out = new File(sdir, PREFIX + name + ASCII_KEYFILE_SUFFIX); + boolean success = KeyStoreUtil.exportCert(ks, ksPass, keyAlias, out); + if (!success) + error("Error getting SSL cert to save as ASCII"); + } else { + error("Error saving ASCII SSL keys"); + } + } + + /** + * Sets up the SSLContext and sets the socket factory. + * No option prefix allowed. + * + * @throws IOException; GeneralSecurityExceptions are wrapped in IOE for convenience + * @return factory, throws on all errors + */ + public static SSLServerSocketFactory initializeFactory(Properties opts) throws IOException { + String ksPass = opts.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD); + String keyPass = opts.getProperty(PROP_KEY_PASSWORD); + if (keyPass == null) { + throw new IOException("No key password, set " + PROP_KEY_PASSWORD + " in " + + (new File(I2PAppContext.getGlobalContext().getConfigDir(), SAMBridge.DEFAULT_SAM_CONFIGFILE)).getAbsolutePath()); + } + String ksname = opts.getProperty(PROP_KS_NAME); + if (ksname == null) { + throw new IOException("No keystore, set " + PROP_KS_NAME + " in " + + (new File(I2PAppContext.getGlobalContext().getConfigDir(), SAMBridge.DEFAULT_SAM_CONFIGFILE)).getAbsolutePath()); + } + File ks = new File(ksname); + if (!ks.isAbsolute()) { + ks = new File(I2PAppContext.getGlobalContext().getConfigDir(), KS_DIR); + ks = new File(ks, ksname); + } + + InputStream fis = null; + try { + SSLContext sslc = SSLContext.getInstance("TLS"); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + fis = new FileInputStream(ks); + keyStore.load(fis, ksPass.toCharArray()); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, keyPass.toCharArray()); + sslc.init(kmf.getKeyManagers(), null, I2PAppContext.getGlobalContext().random()); + return sslc.getServerSocketFactory(); + } catch (GeneralSecurityException gse) { + IOException ioe = new IOException("keystore error"); + ioe.initCause(gse); + throw ioe; + } finally { + if (fis != null) try { fis.close(); } catch (IOException ioe) {} + } + } + + private static void error(String s) { + I2PAppContext.getGlobalContext().logManager().getLog(SSLUtil.class).error(s); + } + + private static void logAlways(String s) { + I2PAppContext.getGlobalContext().logManager().getLog(SSLUtil.class).logAlways(Log.INFO, s); + } +} From 876729c24ecd79ab6b34abcaad89bd093688333b Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 26 Jun 2015 18:51:03 +0000 Subject: [PATCH 04/46] Add protocol and port notification --- .../src/net/i2p/sam/SAMDatagramReceiver.java | 5 +++- .../src/net/i2p/sam/SAMDatagramSession.java | 4 +-- apps/sam/java/src/net/i2p/sam/SAMHandler.java | 4 +-- .../src/net/i2p/sam/SAMMessageSession.java | 21 ++++++++++----- .../java/src/net/i2p/sam/SAMRawReceiver.java | 5 +++- .../java/src/net/i2p/sam/SAMRawSession.java | 4 +-- .../java/src/net/i2p/sam/SAMv1Handler.java | 23 +++++++++++----- .../src/net/i2p/sam/SAMv3DatagramSession.java | 13 ++++++--- .../java/src/net/i2p/sam/SAMv3Handler.java | 24 +++++++++++++---- .../java/src/net/i2p/sam/SAMv3RawSession.java | 4 +-- .../src/net/i2p/sam/SAMv3StreamSession.java | 27 ++++++++++++------- 11 files changed, 95 insertions(+), 39 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java b/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java index 68971af31..b07ff8fbe 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java +++ b/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java @@ -22,9 +22,12 @@ interface SAMDatagramReceiver { * * @param sender Destination * @param data Byte array to be received + * @param proto I2CP protocol + * @param fromPort I2CP from port + * @param toPort I2CP to port * @throws IOException */ - public void receiveDatagramBytes(Destination sender, byte data[]) throws IOException; + public void receiveDatagramBytes(Destination sender, byte data[], int proto, int fromPort, int toPort) throws IOException; /** * Stop receiving data. diff --git a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java index ce3c1803c..8324082be 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java @@ -95,7 +95,7 @@ class SAMDatagramSession extends SAMMessageSession { I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED); } - protected void messageReceived(byte[] msg) { + protected void messageReceived(byte[] msg, int proto, int fromPort, int toPort) { byte[] payload; Destination sender; try { @@ -117,7 +117,7 @@ class SAMDatagramSession extends SAMMessageSession { } try { - recv.receiveDatagramBytes(sender, payload); + recv.receiveDatagramBytes(sender, payload, proto, fromPort, toPort); } catch (IOException e) { _log.error("Error forwarding message to receiver", e); close(); diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandler.java b/apps/sam/java/src/net/i2p/sam/SAMHandler.java index 56d68878c..c7ec10388 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMHandler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMHandler.java @@ -35,8 +35,8 @@ abstract class SAMHandler implements Runnable, Handler { private final Object socketWLock = new Object(); // Guards writings on socket protected final SocketChannel socket; - protected final int verMajor; - protected final int verMinor; + public final int verMajor; + public final int verMinor; /** I2CP options configuring the I2CP connection (port, host, numHops, etc) */ protected final Properties i2cpProps; diff --git a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java index db2450b01..a9531fd1e 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java @@ -18,7 +18,7 @@ import net.i2p.client.I2PClient; import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; -import net.i2p.client.I2PSessionListener; +import net.i2p.client.I2PSessionMuxedListener; import net.i2p.data.Base64; import net.i2p.data.DataFormatException; import net.i2p.data.Destination; @@ -135,7 +135,7 @@ abstract class SAMMessageSession { * Handle a new received message * @param msg Message payload */ - protected abstract void messageReceived(byte[] msg); + protected abstract void messageReceived(byte[] msg, int proto, int fromPort, int toPort); /** * Do whatever is needed to shutdown the SAM session @@ -157,7 +157,7 @@ abstract class SAMMessageSession { * * @author human */ - class SAMMessageSessionHandler implements Runnable, I2PSessionListener { + class SAMMessageSessionHandler implements Runnable, I2PSessionMuxedListener { private final Object runningLock = new Object(); private volatile boolean stillRunning = true; @@ -186,7 +186,7 @@ abstract class SAMMessageSession { if (_log.shouldLog(Log.DEBUG)) _log.debug("I2P session connected"); - session.setSessionListener(this); + session.addMuxedSessionListener(this, I2PSession.PROTO_ANY, I2PSession.PORT_ANY); } /** @@ -217,6 +217,7 @@ abstract class SAMMessageSession { _log.debug("Shutting down SAM message-based session handler"); shutDown(); + session.removeListener(I2PSession.PROTO_ANY, I2PSession.PORT_ANY); try { if (_log.shouldLog(Log.DEBUG)) @@ -242,7 +243,15 @@ abstract class SAMMessageSession { stopRunning(); } - public void messageAvailable(I2PSession session, int msgId, long size){ + public void messageAvailable(I2PSession session, int msgId, long size) { + messageAvailable(session, msgId, size, I2PSession.PROTO_UNSPECIFIED, + I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED); + } + + /** @since 0.9.22 */ + public void messageAvailable(I2PSession session, int msgId, long size, + int proto, int fromPort, int toPort) { + if (_log.shouldLog(Log.DEBUG)) { _log.debug("I2P message available (id: " + msgId + "; size: " + size + ")"); @@ -256,7 +265,7 @@ abstract class SAMMessageSession { + HexDump.dump(msg)); } - messageReceived(msg); + messageReceived(msg, proto, fromPort, toPort); } catch (I2PSessionException e) { _log.error("Error fetching I2P message", e); stopRunning(); diff --git a/apps/sam/java/src/net/i2p/sam/SAMRawReceiver.java b/apps/sam/java/src/net/i2p/sam/SAMRawReceiver.java index 96ebe45d0..564f95c3d 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMRawReceiver.java +++ b/apps/sam/java/src/net/i2p/sam/SAMRawReceiver.java @@ -20,9 +20,12 @@ interface SAMRawReceiver { * regarding the sender. * * @param data Byte array to be received + * @param proto I2CP protocol + * @param fromPort I2CP from port + * @param toPort I2CP to port * @throws IOException */ - public void receiveRawBytes(byte data[]) throws IOException; + public void receiveRawBytes(byte data[], int proto, int fromPort, int toPort) throws IOException; /** * Stop receiving data. diff --git a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java index e3c421608..666d87049 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java @@ -80,9 +80,9 @@ class SAMRawSession extends SAMMessageSession { I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED); } - protected void messageReceived(byte[] msg) { + protected void messageReceived(byte[] msg, int proto, int fromPort, int toPort) { try { - recv.receiveRawBytes(msg); + recv.receiveRawBytes(msg, proto, fromPort, toPort); } catch (IOException e) { _log.error("Error forwarding message to receiver", e); close(); diff --git a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java index cf4d46b62..85322d76b 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java @@ -796,16 +796,21 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece } // SAMRawReceiver implementation - public void receiveRawBytes(byte data[]) throws IOException { + public void receiveRawBytes(byte data[], int proto, int fromPort, int toPort) throws IOException { if (getRawSession() == null) { _log.error("BUG! Received raw bytes, but session is null!"); return; } - ByteArrayOutputStream msg = new ByteArrayOutputStream(); + ByteArrayOutputStream msg = new ByteArrayOutputStream(64 + data.length); - String msgText = "RAW RECEIVED SIZE=" + data.length + "\n"; + String msgText = "RAW RECEIVED SIZE=" + data.length; msg.write(DataHelper.getASCII(msgText)); + if ((verMajor == 3 && verMinor >= 2) || verMajor > 3) { + msgText = " PROTOCOL=" + proto + " FROM_PORT=" + fromPort + " TO_PORT=" + toPort; + msg.write(DataHelper.getASCII(msgText)); + } + msg.write((byte) '\n'); msg.write(data); if (_log.shouldLog(Log.DEBUG)) @@ -832,17 +837,23 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece } // SAMDatagramReceiver implementation - public void receiveDatagramBytes(Destination sender, byte data[]) throws IOException { + public void receiveDatagramBytes(Destination sender, byte data[], int proto, + int fromPort, int toPort) throws IOException { if (getDatagramSession() == null) { _log.error("BUG! Received datagram bytes, but session is null!"); return; } - ByteArrayOutputStream msg = new ByteArrayOutputStream(); + ByteArrayOutputStream msg = new ByteArrayOutputStream(100 + data.length); String msgText = "DATAGRAM RECEIVED DESTINATION=" + sender.toBase64() - + " SIZE=" + data.length + "\n"; + + " SIZE=" + data.length; msg.write(DataHelper.getASCII(msgText)); + if ((verMajor == 3 && verMinor >= 2) || verMajor > 3) { + msgText = " FROM_PORT=" + fromPort + " TO_PORT=" + toPort; + msg.write(DataHelper.getASCII(msgText)); + } + msg.write((byte) '\n'); if (_log.shouldLog(Log.DEBUG)) _log.debug("sending to client: " + msgText); diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java index a16c92327..268083b63 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java @@ -71,11 +71,18 @@ class SAMv3DatagramSession extends SAMDatagramSession implements SAMv3Handler.Se } } - public void receiveDatagramBytes(Destination sender, byte[] data) throws IOException { + public void receiveDatagramBytes(Destination sender, byte[] data, int proto, + int fromPort, int toPort) throws IOException { if (this.clientAddress==null) { - this.handler.receiveDatagramBytes(sender, data); + this.handler.receiveDatagramBytes(sender, data, proto, fromPort, toPort); } else { - String msg = sender.toBase64()+"\n"; + StringBuilder buf = new StringBuilder(600); + buf.append(sender.toBase64()); + if ((handler.verMajor == 3 && handler.verMinor >= 2) || handler.verMajor > 3) { + buf.append(" FROM_PORT=").append(fromPort).append(" TO_PORT=").append(toPort); + } + buf.append('\n'); + String msg = buf.toString(); ByteBuffer msgBuf = ByteBuffer.allocate(msg.length()+data.length); msgBuf.put(DataHelper.getASCII(msg)); msgBuf.put(data); diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index 5e75dcf66..445f8d10a 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -50,7 +50,7 @@ class SAMv3Handler extends SAMv1Handler public static final SessionsDB sSessionsHash = new SessionsDB(); private volatile boolean stolenSocket; private volatile boolean streamForwardingSocket; - + private final boolean sendPorts; interface Session { String getNick(); @@ -88,6 +88,7 @@ class SAMv3Handler extends SAMv1Handler Properties i2cpProps, SAMBridge parent) throws SAMException, IOException { super(s, verMajor, verMinor, i2cpProps, parent); + sendPorts = (verMajor == 3 && verMinor >= 2) || verMajor > 3; if (_log.shouldLog(Log.DEBUG)) _log.debug("SAM version 3 handler instantiated"); } @@ -808,7 +809,7 @@ class SAMv3Handler extends SAMv1Handler try { try { streamForwardingSocket = true ; - ((SAMv3StreamSession)streamSession).startForwardingIncoming(props); + ((SAMv3StreamSession)streamSession).startForwardingIncoming(props, sendPorts); notifyStreamResult( true, "OK", null ); return true ; } catch (SAMException e) { @@ -858,13 +859,18 @@ class SAMv3Handler extends SAMv1Handler } } - public void notifyStreamIncomingConnection(Destination d) throws IOException { + public void notifyStreamIncomingConnection(Destination d, int fromPort, int toPort) throws IOException { if (getStreamSession() == null) { _log.error("BUG! Received stream connection, but session is null!"); throw new NullPointerException("BUG! STREAM session is null!"); } - - if (!writeString(d.toBase64() + "\n")) { + StringBuilder buf = new StringBuilder(600); + buf.append(d.toBase64()); + if (sendPorts) { + buf.append(" FROM_PORT=").append(fromPort).append(" TO_PORT=").append(toPort); + } + buf.append('\n'); + if (!writeString(buf.toString())) { throw new IOException("Error notifying connection to SAM client"); } } @@ -874,6 +880,14 @@ class SAMv3Handler extends SAMv1Handler throw new IOException("Error notifying connection to SAM client"); } } + + /** @since 0.9.22 */ + public static void notifyStreamIncomingConnection(SocketChannel client, Destination d, + int fromPort, int toPort) throws IOException { + if (!writeString(d.toBase64() + " FROM_PORT=" + fromPort + " TO_PORT=" + toPort + '\n', client)) { + throw new IOException("Error notifying connection to SAM client"); + } + } } diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java index 90eae76f9..a228a8943 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java @@ -77,9 +77,9 @@ class SAMv3RawSession extends SAMRawSession implements SAMv3Handler.Session, SA } } - public void receiveRawBytes(byte[] data) throws IOException { + public void receiveRawBytes(byte[] data, int proto, int fromPort, int toPort) throws IOException { if (this.clientAddress==null) { - this.handler.receiveRawBytes(data); + this.handler.receiveRawBytes(data, proto, fromPort, toPort); } else { ByteBuffer msgBuf = ByteBuffer.allocate(data.length); msgBuf.put(data); diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java index fe9561ced..544635aae 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java @@ -166,9 +166,10 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi if ( rec==null || i2ps==null ) throw new InterruptedIOException() ; - if (verbose) - handler.notifyStreamIncomingConnection(i2ps.getPeerDestination()) ; - + if (verbose) { + handler.notifyStreamIncomingConnection(i2ps.getPeerDestination(), + i2ps.getPort(), i2ps.getLocalPort()); + } handler.stealSocket() ; ReadableByteChannel fromClient = handler.getClientSocket(); ReadableByteChannel fromI2P = Channels.newChannel(i2ps.getInputStream()); @@ -185,7 +186,7 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi } - public void startForwardingIncoming( Properties props ) throws SAMException, InterruptedIOException + public void startForwardingIncoming(Properties props, boolean sendPorts) throws SAMException, InterruptedIOException { SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); boolean verbose = props.getProperty("SILENT", "false").equals("false"); @@ -218,7 +219,7 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi this.socketServer = this.socketMgr.getServerSocket(); } - SocketForwarder forwarder = new SocketForwarder(host, port, this, verbose); + SocketForwarder forwarder = new SocketForwarder(host, port, this, verbose, sendPorts); (new Thread(rec.getThreadGroup(), forwarder, "SAMV3StreamForwarder")).start(); } @@ -227,13 +228,14 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi private final String host; private final int port; private final SAMv3StreamSession session; - private final boolean verbose; + private final boolean verbose, sendPorts; - SocketForwarder(String host, int port, SAMv3StreamSession session, boolean verbose) { + SocketForwarder(String host, int port, SAMv3StreamSession session, boolean verbose, boolean sendPorts) { this.host = host ; this.port = port ; this.session = session ; this.verbose = verbose ; + this.sendPorts = sendPorts; } public void run() @@ -264,9 +266,16 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi // build pipes between both sockets try { clientServerSock.socket().setKeepAlive(true); - if (this.verbose) - SAMv3Handler.notifyStreamIncomingConnection( + if (this.verbose) { + if (sendPorts) { + SAMv3Handler.notifyStreamIncomingConnection( + clientServerSock, i2ps.getPeerDestination(), + i2ps.getPort(), i2ps.getLocalPort()); + } else { + SAMv3Handler.notifyStreamIncomingConnection( clientServerSock, i2ps.getPeerDestination()); + } + } ReadableByteChannel fromClient = clientServerSock ; ReadableByteChannel fromI2P = Channels.newChannel(i2ps.getInputStream()); WritableByteChannel toClient = clientServerSock ; From 33672e6a86dfa3a0fc691b21b985654d07896d4f Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 26 Jun 2015 20:24:15 +0000 Subject: [PATCH 05/46] Add authorization New PasswordManager methods for use by SAM --- apps/sam/java/src/net/i2p/sam/SAMBridge.java | 3 ++ .../src/net/i2p/sam/SAMHandlerFactory.java | 15 ++++++++++ .../src/net/i2p/util/PasswordManager.java | 29 +++++++++++++++++++ .../router/util/RouterPasswordManager.java | 8 +---- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMBridge.java b/apps/sam/java/src/net/i2p/sam/SAMBridge.java index 77ec2cffe..4c67d1412 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMBridge.java +++ b/apps/sam/java/src/net/i2p/sam/SAMBridge.java @@ -85,6 +85,9 @@ public class SAMBridge implements Runnable, ClientApp { private static final String PROP_SAM_SSL = "sam.useSSL"; public static final String PROP_TCP_HOST = "sam.tcp.host"; public static final String PROP_TCP_PORT = "sam.tcp.port"; + public static final String PROP_AUTH = "sam.auth"; + public static final String PROP_PW_PREFIX = "sam.auth."; + public static final String PROP_PW_SUFFIX = ".shash"; protected static final String DEFAULT_TCP_HOST = "127.0.0.1"; protected static final String DEFAULT_TCP_PORT = "7656"; diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java index dc4c8f24f..49bb72bd9 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java +++ b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java @@ -18,6 +18,7 @@ import java.util.StringTokenizer; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.util.Log; +import net.i2p.util.PasswordManager; import net.i2p.util.VersionComparator; /** @@ -93,6 +94,20 @@ class SAMHandlerFactory { SAMHandler.writeString("HELLO REPLY RESULT=NOVERSION\n", s); return null; } + + if (Boolean.valueOf(i2cpProps.getProperty(SAMBridge.PROP_AUTH))) { + String user = props.getProperty("USER"); + String pw = props.getProperty("PASSWORD"); + if (user == null || pw == null) + throw new SAMException("USER and PASSWORD required"); + String savedPW = i2cpProps.getProperty(SAMBridge.PROP_PW_PREFIX + user + SAMBridge.PROP_PW_SUFFIX); + if (savedPW == null) + throw new SAMException("Authorization failed"); + PasswordManager pm = new PasswordManager(I2PAppContext.getGlobalContext()); + if (!pm.checkHash(savedPW, pw)) + throw new SAMException("Authorization failed"); + } + // Let's answer positively if (!SAMHandler.writeString("HELLO REPLY RESULT=OK VERSION=" + ver + "\n", s)) throw new SAMException("Error writing to socket"); diff --git a/core/java/src/net/i2p/util/PasswordManager.java b/core/java/src/net/i2p/util/PasswordManager.java index 5e51dcb7d..e264254fc 100644 --- a/core/java/src/net/i2p/util/PasswordManager.java +++ b/core/java/src/net/i2p/util/PasswordManager.java @@ -99,6 +99,18 @@ public class PasswordManager { String shash = _context.getProperty(pfx + PROP_SHASH); if (shash == null) return false; + return checkHash(shash, pw); + } + + /** + * Check pw against b64 salt+hash, as generated by createHash() + * + * @param shash b64 string + * @param pw plain text non-null, already trimmed + * @return if pw verified + * @since 0.9.22 + */ + public boolean checkHash(String shash, String pw) { byte[] shashBytes = Base64.decode(shash); if (shashBytes == null || shashBytes.length != SHASH_LENGTH) return false; @@ -110,6 +122,23 @@ public class PasswordManager { return DataHelper.eq(hash, pwHash); } + /** + * Create a salt+hash, to be saved and verified later by verifyHash(). + * + * @param pw plain text non-null, already trimmed + * @return salted+hash b64 string + * @since 0.9.22 + */ + public String createHash(String pw) { + byte[] salt = new byte[SALT_LENGTH]; + _context.random().nextBytes(salt); + byte[] pwHash = _context.keyGenerator().generateSessionKey(salt, DataHelper.getUTF8(pw)).getData(); + byte[] shashBytes = new byte[SHASH_LENGTH]; + System.arraycopy(salt, 0, shashBytes, 0, SALT_LENGTH); + System.arraycopy(pwHash, 0, shashBytes, SALT_LENGTH, SessionKey.KEYSIZE_BYTES); + return Base64.encode(shashBytes); + } + /** * Either plain or b64 * diff --git a/router/java/src/net/i2p/router/util/RouterPasswordManager.java b/router/java/src/net/i2p/router/util/RouterPasswordManager.java index 97ef2468d..3c2fb9b7d 100644 --- a/router/java/src/net/i2p/router/util/RouterPasswordManager.java +++ b/router/java/src/net/i2p/router/util/RouterPasswordManager.java @@ -158,13 +158,7 @@ public class RouterPasswordManager extends PasswordManager { String pfx = realm; if (user != null && user.length() > 0) pfx += '.' + user; - byte[] salt = new byte[SALT_LENGTH]; - _context.random().nextBytes(salt); - byte[] pwHash = _context.keyGenerator().generateSessionKey(salt, DataHelper.getUTF8(pw)).getData(); - byte[] shashBytes = new byte[SHASH_LENGTH]; - System.arraycopy(salt, 0, shashBytes, 0, SALT_LENGTH); - System.arraycopy(pwHash, 0, shashBytes, SALT_LENGTH, SessionKey.KEYSIZE_BYTES); - String shash = Base64.encode(shashBytes); + String shash = createHash(pw); Map toAdd = Collections.singletonMap(pfx + PROP_SHASH, shash); List toDel = new ArrayList(4); toDel.add(pfx + PROP_PW); From b82c1ead723ca6086699c7babeb111efa8ead090 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 26 Jun 2015 21:32:24 +0000 Subject: [PATCH 06/46] Add AUTH commands: ENABLE, DISABLE, ADD, REMOVE Store changes to config file --- apps/sam/java/src/net/i2p/sam/SAMBridge.java | 20 ++++++++-- .../java/src/net/i2p/sam/SAMv3Handler.java | 38 +++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMBridge.java b/apps/sam/java/src/net/i2p/sam/SAMBridge.java index 4c67d1412..4b9a3293c 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMBridge.java +++ b/apps/sam/java/src/net/i2p/sam/SAMBridge.java @@ -58,6 +58,7 @@ public class SAMBridge implements Runnable, ClientApp { private final int _listenPort; private final Properties i2cpProps; private final boolean _useSSL; + private final File _configFile; private volatile Thread _runner; /** @@ -117,6 +118,7 @@ public class SAMBridge implements Runnable, ClientApp { if (_useSSL && !SystemVersion.isJava7()) throw new IllegalArgumentException("SSL requires Java 7 or higher"); persistFilename = options.keyFile; + _configFile = options.configFile; nameToPrivKeys = new HashMap(8); _handlers = new HashSet(8); this.i2cpProps = options.opts; @@ -140,7 +142,8 @@ public class SAMBridge implements Runnable, ClientApp { * @param persistFile location to store/load named keys to/from * @throws RuntimeException if a server socket can't be opened */ - public SAMBridge(String listenHost, int listenPort, boolean isSSL, Properties i2cpProps, String persistFile) { + public SAMBridge(String listenHost, int listenPort, boolean isSSL, Properties i2cpProps, + String persistFile, File configFile) { _log = I2PAppContext.getGlobalContext().logManager().getLog(SAMBridge.class); _mgr = null; _listenHost = listenHost; @@ -150,6 +153,7 @@ public class SAMBridge implements Runnable, ClientApp { throw new IllegalArgumentException("SSL requires Java 7 or higher"); this.i2cpProps = i2cpProps; persistFilename = persistFile; + _configFile = configFile; nameToPrivKeys = new HashMap(8); _handlers = new HashSet(8); loadKeys(); @@ -451,7 +455,8 @@ public class SAMBridge implements Runnable, ClientApp { public static void main(String args[]) { try { Options options = getOptions(args); - SAMBridge bridge = new SAMBridge(options.host, options.port, options.isSSL, options.opts, options.keyFile); + SAMBridge bridge = new SAMBridge(options.host, options.port, options.isSSL, options.opts, + options.keyFile, options.configFile); bridge.startThread(); } catch (RuntimeException e) { e.printStackTrace(); @@ -490,10 +495,12 @@ public class SAMBridge implements Runnable, ClientApp { private final int port; private final Properties opts; private final boolean isSSL; + private final File configFile; - public Options(String host, int port, boolean isSSL, Properties opts, String keyFile) { + public Options(String host, int port, boolean isSSL, Properties opts, String keyFile, File configFile) { this.host = host; this.port = port; this.opts = opts; this.keyFile = keyFile; this.isSSL = isSSL; + this.configFile = configFile; } } @@ -614,7 +621,7 @@ public class SAMBridge implements Runnable, ClientApp { if (remaining > 0) { parseOptions(args, startOpts, opts); } - return new Options(host, port, isSSL, opts, keyfile); + return new Options(host, port, isSSL, opts, keyfile, file); } /** @@ -742,4 +749,9 @@ public class SAMBridge implements Runnable, ClientApp { changeState(STOPPED); } } + + /** @since 0.9.22 */ + public void saveConfig() throws IOException { + DataHelper.storeProps(i2cpProps, _configFile); + } } diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index 445f8d10a..f623798dc 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -36,6 +36,7 @@ import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.util.Log; import net.i2p.util.I2PAppThread; +import net.i2p.util.PasswordManager; /** * Class able to handle a SAM version 3 client connection. @@ -425,6 +426,8 @@ class SAMv3Handler extends SAMv1Handler } else if (domain.equals("RAW")) { // TODO not yet overridden, ID is ignored, most recent RAW session is used canContinue = execRawMessage(opcode, props); + } else if (domain.equals("AUTH")) { + canContinue = execAuthMessage(opcode, props); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Unrecognized message domain: \"" @@ -889,5 +892,40 @@ class SAMv3Handler extends SAMv1Handler } } + /** @since 0.9.22 */ + private boolean execAuthMessage(String opcode, Properties props) { + if (opcode.equals("ENABLE")) { + i2cpProps.setProperty(SAMBridge.PROP_AUTH, "true"); + } else if (opcode.equals("DISABLE")) { + i2cpProps.setProperty(SAMBridge.PROP_AUTH, "false"); + } else if (opcode.equals("ADD")) { + String user = props.getProperty("USER"); + String pw = props.getProperty("PASSWORD"); + if (user == null || pw == null) + return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"USER and PASSWORD required\"\n"); + String prop = SAMBridge.PROP_PW_PREFIX + user + SAMBridge.PROP_PW_SUFFIX; + if (i2cpProps.containsKey(prop)) + return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"user " + user + " already exists\"\n"); + PasswordManager pm = new PasswordManager(I2PAppContext.getGlobalContext()); + String shash = pm.createHash(pw); + i2cpProps.setProperty(prop, shash); + } else if (opcode.equals("REMOVE")) { + String user = props.getProperty("USER"); + if (user == null) + return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"USER required\"\n"); + String prop = SAMBridge.PROP_PW_PREFIX + user + SAMBridge.PROP_PW_SUFFIX; + if (!i2cpProps.containsKey(prop)) + return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"user " + user + " not found\"\n"); + i2cpProps.remove(prop); + } else { + return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown AUTH command\"\n"); + } + try { + bridge.saveConfig(); + return writeString("AUTH STATUS RESULT=OK\n"); + } catch (IOException ioe) { + return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"Config save failed: " + ioe + "\"\n"); + } + } } From 49e68bcc86ddb1a19bf026cbe89f05b19d687f09 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 26 Jun 2015 21:47:37 +0000 Subject: [PATCH 07/46] ports for CONNECT --- .../java/src/net/i2p/sam/SAMv3StreamSession.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java index 544635aae..b5d613d18 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java @@ -97,6 +97,22 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi I2PSocketOptions opts = socketMgr.buildOptions(props); if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null) opts.setConnectTimeout(60 * 1000); + String fromPort = props.getProperty("FROM_PORT"); + if (fromPort != null) { + try { + opts.setLocalPort(Integer.parseInt(fromPort)); + } catch (NumberFormatException nfe) { + throw new I2PException("Bad port " + fromPort); + } + } + String toPort = props.getProperty("TO_PORT"); + if (toPort != null) { + try { + opts.setPort(Integer.parseInt(toPort)); + } catch (NumberFormatException nfe) { + throw new I2PException("Bad port " + toPort); + } + } if (_log.shouldLog(Log.DEBUG)) _log.debug("Connecting new I2PSocket..."); From 12385f04ec1bc6f76cd3bcef6f2b477f01a29e0a Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 26 Jun 2015 23:12:01 +0000 Subject: [PATCH 08/46] protocol and ports for outgoing datagrams --- .../src/net/i2p/sam/SAMDatagramSession.java | 8 +- .../src/net/i2p/sam/SAMMessageSession.java | 3 +- .../java/src/net/i2p/sam/SAMRawSession.java | 8 +- .../java/src/net/i2p/sam/SAMv1Handler.java | 116 +++++++++++++----- .../java/src/net/i2p/sam/SAMv3Handler.java | 76 ++++++++++-- .../src/net/i2p/sam/SAMv3StreamSession.java | 6 +- 6 files changed, 159 insertions(+), 58 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java index 8324082be..d4f026d98 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java @@ -12,7 +12,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.Properties; -import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.client.datagram.I2PDatagramDissector; import net.i2p.client.datagram.I2PDatagramMaker; @@ -83,16 +82,15 @@ class SAMDatagramSession extends SAMMessageSession { * @throws DataFormatException on unknown / bad dest * @throws I2PSessionException on serious error, probably session closed */ - public boolean sendBytes(String dest, byte[] data) throws DataFormatException, I2PSessionException { + public boolean sendBytes(String dest, byte[] data, int proto, + int fromPort, int toPort) throws DataFormatException, I2PSessionException { if (data.length > DGRAM_SIZE_MAX) throw new DataFormatException("Datagram size exceeded (" + data.length + ")"); byte[] dgram ; synchronized (dgramMaker) { dgram = dgramMaker.makeI2PDatagram(data); } - // TODO pass ports through - return sendBytesThroughMessageSession(dest, dgram, I2PSession.PROTO_DATAGRAM, - I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED); + return sendBytesThroughMessageSession(dest, dgram, proto, fromPort, toPort); } protected void messageReceived(byte[] msg, int proto, int fromPort, int toPort) { diff --git a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java index a9531fd1e..8613ed2d0 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java @@ -96,7 +96,8 @@ abstract class SAMMessageSession { * @throws DataFormatException on unknown / bad dest * @throws I2PSessionException on serious error, probably session closed */ - public abstract boolean sendBytes(String dest, byte[] data) throws DataFormatException, I2PSessionException; + public abstract boolean sendBytes(String dest, byte[] data, int proto, + int fromPort, int toPort) throws DataFormatException, I2PSessionException; /** * Actually send bytes through the SAM message-based session I2PSession diff --git a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java index 666d87049..e31f5950c 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java @@ -12,7 +12,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.Properties; -import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.data.DataFormatException; import net.i2p.util.Log; @@ -72,12 +71,11 @@ class SAMRawSession extends SAMMessageSession { * @throws DataFormatException on unknown / bad dest * @throws I2PSessionException on serious error, probably session closed */ - public boolean sendBytes(String dest, byte[] data) throws DataFormatException, I2PSessionException { + public boolean sendBytes(String dest, byte[] data, int proto, + int fromPort, int toPort) throws DataFormatException, I2PSessionException { if (data.length > RAW_SIZE_MAX) throw new DataFormatException("Data size limit exceeded (" + data.length + ")"); - // TODO pass ports through - return sendBytesThroughMessageSession(dest, data, I2PSession.PROTO_DATAGRAM_RAW, - I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED); + return sendBytesThroughMessageSession(dest, data, proto, fromPort, toPort); } protected void messageReceived(byte[] msg, int proto, int fromPort, int toPort) { diff --git a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java index 85322d76b..a0b5d5309 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicLong; import net.i2p.I2PException; import net.i2p.client.I2PClient; +import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.crypto.SigType; import net.i2p.data.Base64; @@ -438,25 +439,44 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece } int size; - { - String strsize = props.getProperty("SIZE"); - if (strsize == null) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Size not specified in DATAGRAM SEND message"); - return false; - } + String strsize = props.getProperty("SIZE"); + if (strsize == null) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Size not specified in DATAGRAM SEND message"); + return false; + } + try { + size = Integer.parseInt(strsize); + } catch (NumberFormatException e) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid DATAGRAM SEND size specified: " + strsize); + return false; + } + if (!checkDatagramSize(size)) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Specified size (" + size + + ") is out of protocol limits"); + return false; + } + int proto = I2PSession.PROTO_DATAGRAM; + int fromPort = I2PSession.PORT_UNSPECIFIED; + int toPort = I2PSession.PORT_UNSPECIFIED; + String s = props.getProperty("FROM_PORT"); + if (s != null) { try { - size = Integer.parseInt(strsize); + fromPort = Integer.parseInt(s); } catch (NumberFormatException e) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Invalid DATAGRAM SEND size specified: " + strsize); - return false; + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid DATAGRAM SEND port specified: " + s); } - if (!checkDatagramSize(size)) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Specified size (" + size - + ") is out of protocol limits"); - return false; + } + s = props.getProperty("TO_PORT"); + if (s != null) { + try { + toPort = Integer.parseInt(s); + } catch (NumberFormatException e) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid RAW SEND port specified: " + s); } } @@ -466,7 +486,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece in.readFully(data); - if (!getDatagramSession().sendBytes(dest, data)) { + if (!getDatagramSession().sendBytes(dest, data, proto, fromPort, toPort)) { _log.error("DATAGRAM SEND failed"); // a message send failure is no reason to drop the SAM session // for raw and repliable datagrams, just carry on our merry way @@ -523,25 +543,53 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece } int size; - { - String strsize = props.getProperty("SIZE"); - if (strsize == null) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Size not specified in RAW SEND message"); - return false; - } + String strsize = props.getProperty("SIZE"); + if (strsize == null) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Size not specified in RAW SEND message"); + return false; + } + try { + size = Integer.parseInt(strsize); + } catch (NumberFormatException e) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid RAW SEND size specified: " + strsize); + return false; + } + if (!checkSize(size)) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Specified size (" + size + + ") is out of protocol limits"); + return false; + } + int proto = I2PSession.PROTO_DATAGRAM_RAW; + int fromPort = I2PSession.PORT_UNSPECIFIED; + int toPort = I2PSession.PORT_UNSPECIFIED; + String s = props.getProperty("PROTOCOL"); + if (s != null) { try { - size = Integer.parseInt(strsize); + proto = Integer.parseInt(s); } catch (NumberFormatException e) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Invalid RAW SEND size specified: " + strsize); - return false; + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid RAW SEND protocol specified: " + s); } - if (!checkSize(size)) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Specified size (" + size - + ") is out of protocol limits"); - return false; + } + s = props.getProperty("FROM_PORT"); + if (s != null) { + try { + fromPort = Integer.parseInt(s); + } catch (NumberFormatException e) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid RAW SEND port specified: " + s); + } + } + s = props.getProperty("TO_PORT"); + if (s != null) { + try { + toPort = Integer.parseInt(s); + } catch (NumberFormatException e) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid RAW SEND port specified: " + s); } } @@ -551,7 +599,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece in.readFully(data); - if (!getRawSession().sendBytes(dest, data)) { + if (!getRawSession().sendBytes(dest, data, proto, fromPort, toPort)) { _log.error("RAW SEND failed"); // a message send failure is no reason to drop the SAM session // for raw and repliable datagrams, just carry on our merry way diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index f623798dc..3a0f55e13 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -28,6 +28,7 @@ import java.util.StringTokenizer; import net.i2p.I2PAppContext; import net.i2p.I2PException; import net.i2p.client.I2PClient; +import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.crypto.SigType; import net.i2p.data.Base64; @@ -56,7 +57,8 @@ class SAMv3Handler extends SAMv1Handler interface Session { String getNick(); void close(); - boolean sendBytes(String dest, byte[] data) throws DataFormatException, I2PSessionException; + boolean sendBytes(String dest, byte[] data, int proto, + int fromPort, int toPort) throws DataFormatException, I2PSessionException; } /** @@ -189,14 +191,16 @@ class SAMv3Handler extends SAMv1Handler try { String header = DataHelper.readLine(is).trim(); StringTokenizer tok = new StringTokenizer(header, " "); - if (tok.countTokens() != 3) { + if (tok.countTokens() < 3) { // This is not a correct message, for sure - //_log.debug("Error in message format"); - // FIXME log? throw? + warn("Bad datagram header received"); return; } String version = tok.nextToken(); - if (!"3.0".equals(version)) return ; + if (!version.startsWith("3")) { + warn("Bad datagram header received"); + return; + } String nick = tok.nextToken(); String dest = tok.nextToken(); @@ -204,18 +208,66 @@ class SAMv3Handler extends SAMv1Handler is.read(data); SessionRecord rec = sSessionsHash.get(nick); if (rec!=null) { - rec.getHandler().session.sendBytes(dest,data); + Properties sprops = rec.getProps(); + String pr = sprops.getProperty("PROTOCOL"); + String fp = sprops.getProperty("FROM_PORT"); + String tp = sprops.getProperty("TO_PORT"); + while (tok.hasMoreTokens()) { + String t = tok.nextToken(); + if (t.startsWith("PROTOCOL=")) + pr = t.substring("PROTOTCOL=".length()); + else if (t.startsWith("FROM_PORT=")) + fp = t.substring("FROM_PORT=".length()); + else if (t.startsWith("TO_PORT=")) + tp = t.substring("TO_PORT=".length()); + } + int proto = I2PSession.PROTO_UNSPECIFIED; + int fromPort = I2PSession.PORT_UNSPECIFIED; + int toPort = I2PSession.PORT_UNSPECIFIED; + if (pr != null) { + try { + proto = Integer.parseInt(pr); + } catch (NumberFormatException nfe) { + warn("Bad datagram header received"); + return; + } + } + if (fp != null) { + try { + fromPort = Integer.parseInt(fp); + } catch (NumberFormatException nfe) { + warn("Bad datagram header received"); + return; + } + } + if (tp != null) { + try { + toPort = Integer.parseInt(tp); + } catch (NumberFormatException nfe) { + warn("Bad datagram header received"); + return; + } + } + rec.getHandler().session.sendBytes(dest,data, proto, fromPort, toPort); } else { - Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3Handler.class); - if (log.shouldLog(Log.WARN)) - log.warn("Dropping datagram, no session for " + nick); + warn("Dropping datagram, no session for " + nick); } } catch (Exception e) { - Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3Handler.class); - if (log.shouldLog(Log.WARN)) - log.warn("Error handling datagram", e); + warn("Error handling datagram", e); } } + + /** @since 0.9.22 */ + private static void warn(String s) { + warn(s, null); + } + + /** @since 0.9.22 */ + private static void warn(String s, Throwable t) { + Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3Handler.class); + if (log.shouldLog(Log.WARN)) + log.warn(s, t); + } } /** diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java index b5d613d18..d927c6f3c 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java @@ -415,7 +415,11 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi socketMgr.destroySocketManager(); } - public boolean sendBytes(String s, byte[] b) throws DataFormatException + /** + * Unsupported + * @throws DataFormatException always + */ + public boolean sendBytes(String s, byte[] b, int pr, int fp, int tp) throws DataFormatException { throw new DataFormatException(null); } From 9b2d416154d5995edb9df571651e9260d9a13597 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 27 Jun 2015 13:15:28 +0000 Subject: [PATCH 09/46] Stub out PING and PONG commands. Handle PING and send PONG. No code for sending PINGs yet. Don't drop connection if only one token. --- .../java/src/net/i2p/sam/SAMv3Handler.java | 52 ++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index 3a0f55e13..01ed1f211 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -197,15 +197,13 @@ class SAMv3Handler extends SAMv1Handler return; } String version = tok.nextToken(); - if (!version.startsWith("3")) { + if (!version.startsWith("3.")) { warn("Bad datagram header received"); return; } String nick = tok.nextToken(); String dest = tok.nextToken(); - byte[] data = new byte[is.available()]; - is.read(data); SessionRecord rec = sSessionsHash.get(nick); if (rec!=null) { Properties sprops = rec.getProps(); @@ -248,6 +246,9 @@ class SAMv3Handler extends SAMv1Handler return; } } + // TODO too many allocations and copies. One here and one in Listener above. + byte[] data = new byte[is.available()]; + is.read(data); rec.getHandler().session.sendBytes(dest,data, proto, fromPort, toPort); } else { warn("Dropping datagram, no session for " + nick); @@ -448,13 +449,29 @@ class SAMv3Handler extends SAMv1Handler } tok = new StringTokenizer(msg, " "); - if (tok.countTokens() < 2) { + int count = tok.countTokens(); + if (count <= 0) { // This is not a correct message, for sure if (_log.shouldLog(Log.DEBUG)) - _log.debug("Error in message format"); - break; + _log.debug("Ignoring whitespace"); + continue; } domain = tok.nextToken(); + // these may not have a second token + if (domain.equals("PING")) { + execPingMessage(tok); + continue; + } else if (domain.equals("PONG")) { + execPongMessage(tok); + continue; + } + if (count <= 1) { + // This is not a correct message, for sure + if (writeString(domain + " STATUS RESULT=I2P_ERROR MESSAGE=\"command not specified\"\n")) + continue; + else + break; + } opcode = tok.nextToken(); if (_log.shouldLog(Log.DEBUG)) { _log.debug("Parsing (domain: \"" + domain @@ -979,5 +996,28 @@ class SAMv3Handler extends SAMv1Handler return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"Config save failed: " + ioe + "\"\n"); } } + + /** + * Handle a PING. + * Send a PONG. + * @since 0.9.22 + */ + private void execPingMessage(StringTokenizer tok) { + StringBuilder buf = new StringBuilder(); + buf.append("PONG"); + while (tok.hasMoreTokens()) { + buf.append(' ').append(tok.nextToken()); + } + buf.append('\n'); + writeString(buf.toString()); + } + + /** + * Handle a PONG. + * @since 0.9.22 + */ + private void execPongMessage(StringTokenizer tok) { + // TODO. We don't send PINGs yet. + } } From 194f20e18c1ad080440b817f50646e2d8754c468 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 27 Jun 2015 14:31:55 +0000 Subject: [PATCH 10/46] V3 Stream Session: SSL for STREAM FORWARD better exception handling boolean cleanups --- .../src/net/i2p/sam/SAMHandlerFactory.java | 2 +- .../java/src/net/i2p/sam/SAMv3Handler.java | 8 +- .../src/net/i2p/sam/SAMv3StreamSession.java | 91 +++++++++++++++---- 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java index 49bb72bd9..fbe1e905a 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java +++ b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java @@ -95,7 +95,7 @@ class SAMHandlerFactory { return null; } - if (Boolean.valueOf(i2cpProps.getProperty(SAMBridge.PROP_AUTH))) { + if (Boolean.parseBoolean(i2cpProps.getProperty(SAMBridge.PROP_AUTH))) { String user = props.getProperty("USER"); String pw = props.getProperty("PASSWORD"); if (user == null || pw == null) diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index 01ed1f211..eb5ff9f64 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -837,7 +837,7 @@ class SAMv3Handler extends SAMv1Handler _log.debug("No parameters specified in STREAM CONNECT message"); return false; } - boolean verbose = props.getProperty("SILENT","false").equals("false"); + boolean verbose = !Boolean.parseBoolean(props.getProperty("SILENT")); String dest = props.getProperty("DESTINATION"); if (dest == null) { @@ -877,7 +877,7 @@ class SAMv3Handler extends SAMv1Handler return false ; } - protected boolean execStreamForwardIncoming( Properties props ) { + private boolean execStreamForwardIncoming( Properties props ) { try { try { streamForwardingSocket = true ; @@ -894,9 +894,9 @@ class SAMv3Handler extends SAMv1Handler return false ; } - protected boolean execStreamAccept( Properties props ) + private boolean execStreamAccept( Properties props ) { - boolean verbose = props.getProperty( "SILENT", "false").equals("false"); + boolean verbose = !Boolean.parseBoolean(props.getProperty("SILENT")); try { try { notifyStreamResult(verbose, "OK", null); diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java index d927c6f3c..3bd98e71c 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java @@ -11,9 +11,16 @@ package net.i2p.sam; import java.io.IOException; import java.io.InterruptedIOException; import java.net.ConnectException; +import java.net.InetSocketAddress; import java.net.NoRouteToHostException; +import java.net.SocketTimeoutException; +import java.security.GeneralSecurityException; import java.util.Properties; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSocket; + +import net.i2p.I2PAppContext; import net.i2p.I2PException; import net.i2p.client.streaming.I2PServerSocket; import net.i2p.client.streaming.I2PSocket; @@ -21,6 +28,7 @@ import net.i2p.client.streaming.I2PSocketOptions; import net.i2p.data.DataFormatException; import net.i2p.data.Destination; import net.i2p.util.I2PAppThread; +import net.i2p.util.I2PSSLSocketFactory; import net.i2p.util.Log; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; @@ -41,6 +49,7 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi private final Object socketServerLock = new Object(); private I2PServerSocket socketServer; + private static I2PSSLSocketFactory _sslSocketFactory; private final String nick ; @@ -91,7 +100,7 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi throws I2PException, ConnectException, NoRouteToHostException, DataFormatException, InterruptedIOException, IOException { - boolean verbose = (props.getProperty("SILENT", "false").equals("false")); + boolean verbose = !Boolean.parseBoolean(props.getProperty("SILENT")); Destination d = SAMUtils.getDest(dest); I2PSocketOptions opts = socketMgr.buildOptions(props); @@ -202,10 +211,13 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi } + /** + * Forward sockets from I2P to the host/port provided + */ public void startForwardingIncoming(Properties props, boolean sendPorts) throws SAMException, InterruptedIOException { SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); - boolean verbose = props.getProperty("SILENT", "false").equals("false"); + boolean verbose = !Boolean.parseBoolean(props.getProperty("SILENT")); if ( rec==null ) throw new InterruptedIOException() ; @@ -223,7 +235,7 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi if (_log.shouldLog(Log.DEBUG)) _log.debug("no host specified. Taken from the client socket : " + host +':'+port); } - + boolean isSSL = Boolean.parseBoolean(props.getProperty("SSL")); synchronized( this.socketServerLock ) { @@ -235,23 +247,28 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi this.socketServer = this.socketMgr.getServerSocket(); } - SocketForwarder forwarder = new SocketForwarder(host, port, this, verbose, sendPorts); + SocketForwarder forwarder = new SocketForwarder(host, port, isSSL, this, verbose, sendPorts); (new Thread(rec.getThreadGroup(), forwarder, "SAMV3StreamForwarder")).start(); } + /** + * Forward sockets from I2P to the host/port provided + */ private static class SocketForwarder implements Runnable { private final String host; private final int port; private final SAMv3StreamSession session; - private final boolean verbose, sendPorts; + private final boolean isSSL, verbose, sendPorts; - SocketForwarder(String host, int port, SAMv3StreamSession session, boolean verbose, boolean sendPorts) { + SocketForwarder(String host, int port, boolean isSSL, + SAMv3StreamSession session, boolean verbose, boolean sendPorts) { this.host = host ; this.port = port ; this.session = session ; this.verbose = verbose ; this.sendPorts = sendPorts; + this.isSSL = isSSL; } public void run() @@ -259,24 +276,62 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi while (session.getSocketServer()!=null) { // wait and accept a connection from I2P side - I2PSocket i2ps = null ; + I2PSocket i2ps; try { i2ps = session.getSocketServer().accept(); - } catch (Exception e) {} - - if (i2ps==null) { - continue ; - } + if (i2ps == null) + continue; + } catch (SocketTimeoutException ste) { + continue; + } catch (ConnectException ce) { + Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3StreamSession.class); + if (log.shouldLog(Log.WARN)) + log.warn("Error accepting", ce); + try { Thread.sleep(50); } catch (InterruptedException ie) {} + continue; + } catch (I2PException ipe) { + Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3StreamSession.class); + if (log.shouldLog(Log.WARN)) + log.warn("Error accepting", ipe); + break; + } // open a socket towards client - java.net.InetSocketAddress addr = new java.net.InetSocketAddress(host,port); - SocketChannel clientServerSock = null ; + SocketChannel clientServerSock; try { - clientServerSock = SocketChannel.open(addr) ; - } - catch ( IOException e ) { - continue ; + if (isSSL) { + I2PAppContext ctx = I2PAppContext.getGlobalContext(); + synchronized(SAMv3StreamSession.class) { + if (_sslSocketFactory == null) { + try { + _sslSocketFactory = new I2PSSLSocketFactory( + ctx, true, "certificates/sam"); + } catch (GeneralSecurityException gse) { + Log log = ctx.logManager().getLog(SAMv3StreamSession.class); + log.error("SSL error", gse); + try { + i2ps.close(); + } catch (IOException ee) {} + throw new RuntimeException("SSL error", gse); + } + } + } + SSLSocket sock = (SSLSocket) _sslSocketFactory.createSocket(host, port); + I2PSSLSocketFactory.verifyHostname(ctx, sock, host); + clientServerSock = new SSLSocketChannel(sock); + } else { + InetSocketAddress addr = new InetSocketAddress(host, port); + clientServerSock = SocketChannel.open(addr) ; + } + } catch (IOException ioe) { + Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3StreamSession.class); + if (log.shouldLog(Log.WARN)) + log.warn("Error forwarding", ioe); + try { + i2ps.close(); + } catch (IOException ee) {} + continue; } // build pipes between both sockets From 246b376ed96b8ae6d74e1a8d563a12da5c2835a5 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 27 Jun 2015 14:58:29 +0000 Subject: [PATCH 11/46] tab cleanup --- .../java/src/net/i2p/sam/SAMv3RawSession.java | 55 ++++++++----------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java index a228a8943..9cee67791 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java @@ -36,45 +36,34 @@ class SAMv3RawSession extends SAMRawSession implements SAMv3Handler.Session, SA * @throws DataFormatException * @throws I2PSessionException */ - public SAMv3RawSession(String nick) - throws IOException, DataFormatException, I2PSessionException { - + public SAMv3RawSession(String nick) throws IOException, DataFormatException, I2PSessionException { super(SAMv3Handler.sSessionsHash.get(nick).getDest(), - SAMv3Handler.sSessionsHash.get(nick).getProps(), - SAMv3Handler.sSessionsHash.get(nick).getHandler() // to be replaced by this - ); + SAMv3Handler.sSessionsHash.get(nick).getProps(), + SAMv3Handler.sSessionsHash.get(nick).getHandler() // to be replaced by this + ); this.nick = nick ; this.recv = this ; // replacement this.server = SAMv3Handler.DatagramServer.getInstance() ; - SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); - if ( rec==null ) throw new InterruptedIOException() ; - - this.handler = rec.getHandler(); - - Properties props = rec.getProps(); - - - String portStr = props.getProperty("PORT") ; - if ( portStr==null ) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("receiver port not specified. Current socket will be used."); - this.clientAddress = null; - } - else { - int port = Integer.parseInt(portStr); - - String host = props.getProperty("HOST"); - if ( host==null ) { - host = rec.getHandler().getClientIP(); - + if (rec == null) + throw new InterruptedIOException() ; + this.handler = rec.getHandler(); + Properties props = rec.getProps(); + String portStr = props.getProperty("PORT") ; + if (portStr == null) { if (_log.shouldLog(Log.DEBUG)) - _log.debug("no host specified. Taken from the client socket : " + host +':'+port); - } - - - this.clientAddress = new InetSocketAddress(host,port); - } + _log.debug("receiver port not specified. Current socket will be used."); + this.clientAddress = null; + } else { + int port = Integer.parseInt(portStr); + String host = props.getProperty("HOST"); + if ( host==null ) { + host = rec.getHandler().getClientIP(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("no host specified. Taken from the client socket : " + host +':'+port); + } + this.clientAddress = new InetSocketAddress(host, port); + } } public void receiveRawBytes(byte[] data, int proto, int fromPort, int toPort) throws IOException { From c662f178233a85f3cd3e4e456a1539138a90ca4e Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 27 Jun 2015 15:41:19 +0000 Subject: [PATCH 12/46] Move DatagramServer from SAMv3Handler to its own file, javadocs more changes to follow --- .../src/net/i2p/sam/SAMv3DatagramServer.java | 217 ++++++++++++++++++ .../src/net/i2p/sam/SAMv3DatagramSession.java | 4 +- .../java/src/net/i2p/sam/SAMv3Handler.java | 184 +-------------- .../java/src/net/i2p/sam/SAMv3RawSession.java | 4 +- 4 files changed, 232 insertions(+), 177 deletions(-) create mode 100644 apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java new file mode 100644 index 000000000..39a9f9034 --- /dev/null +++ b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java @@ -0,0 +1,217 @@ +package net.i2p.sam; +/* + * free (adj.): unencumbered; not under the control of others + * Written by human in 2004 and released into the public domain + * with no warranty of any kind, either expressed or implied. + * It probably won't make your computer catch on fire, or eat + * your children, but it might. Use at your own risk. + * + */ + + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.util.Properties; +import java.util.StringTokenizer; + +import net.i2p.I2PAppContext; +import net.i2p.client.I2PSession; +import net.i2p.data.DataHelper; +import net.i2p.util.I2PAppThread; +import net.i2p.util.Log; + +/** + * This is a singleton listening on 127.0.0.1:7655 or as specified by + * sam.udp.host and sam.udp.port properties. + * This is used for both repliable and raw datagrams. + * + * @since 0.9.22 moved from SAMv3Handler + */ +class SAMv3DatagramServer { + + private static SAMv3DatagramServer _instance; + private static DatagramChannel server; + + /** + * Returns the singleton. + * If this is the first call, will be instantiated and will listen + * on the default host:port 127.0.0.1:7655. + */ + public static SAMv3DatagramServer getInstance() throws IOException { + return getInstance(new Properties()); + } + + /** + * Returns the singleton. + * If this is the first call, will be instantiated and will listen + * on the specified host:port, default 127.0.0.1:7655. + * Properties are sam.udp.host and sam.udp.port. + * + * @param props ignored unless this is the first call + */ + public static SAMv3DatagramServer getInstance(Properties props) throws IOException { + synchronized(SAMv3DatagramServer.class) { + if (_instance==null) + _instance = new SAMv3DatagramServer(props); + return _instance ; + } + } + + private SAMv3DatagramServer(Properties props) throws IOException { + synchronized(SAMv3DatagramServer.class) { + if (server==null) + server = DatagramChannel.open(); + } + + String host = props.getProperty(SAMBridge.PROP_DATAGRAM_HOST, SAMBridge.DEFAULT_DATAGRAM_HOST); + String portStr = props.getProperty(SAMBridge.PROP_DATAGRAM_PORT, SAMBridge.DEFAULT_DATAGRAM_PORT); + int port ; + try { + port = Integer.parseInt(portStr); + } catch (NumberFormatException e) { + port = Integer.parseInt(SAMBridge.DEFAULT_DATAGRAM_PORT); + } + + server.socket().bind(new InetSocketAddress(host, port)); + new I2PAppThread(new Listener(server), "DatagramListener").start(); + } + + public void send(SocketAddress addr, ByteBuffer msg) throws IOException { + server.send(msg, addr); + } + + private static class Listener implements Runnable { + + private final DatagramChannel server; + + public Listener(DatagramChannel server) + { + this.server = server ; + } + public void run() + { + ByteBuffer inBuf = ByteBuffer.allocateDirect(SAMRawSession.RAW_SIZE_MAX+1024); + + while (!Thread.interrupted()) + { + inBuf.clear(); + try { + server.receive(inBuf); + } catch (IOException e) { + break ; + } + inBuf.flip(); + ByteBuffer outBuf = ByteBuffer.wrap(new byte[inBuf.remaining()]); + outBuf.put(inBuf); + outBuf.flip(); + // A new thread for every message is wildly inefficient... + //new I2PAppThread(new MessageDispatcher(outBuf.array()), "MessageDispatcher").start(); + // inline + // Even though we could be sending messages through multiple sessions, + // that isn't a common use case, and blocking should be rare. + // Inside router context, I2CP drops on overflow. + (new MessageDispatcher(outBuf.array())).run(); + } + } + } + + private static class MessageDispatcher implements Runnable { + private final ByteArrayInputStream is; + + public MessageDispatcher(byte[] buf) { + this.is = new ByteArrayInputStream(buf) ; + } + + public void run() { + try { + String header = DataHelper.readLine(is).trim(); + StringTokenizer tok = new StringTokenizer(header, " "); + if (tok.countTokens() < 3) { + // This is not a correct message, for sure + warn("Bad datagram header received"); + return; + } + String version = tok.nextToken(); + if (!version.startsWith("3.")) { + warn("Bad datagram header received"); + return; + } + String nick = tok.nextToken(); + String dest = tok.nextToken(); + + SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); + if (rec!=null) { + Properties sprops = rec.getProps(); + String pr = sprops.getProperty("PROTOCOL"); + String fp = sprops.getProperty("FROM_PORT"); + String tp = sprops.getProperty("TO_PORT"); + while (tok.hasMoreTokens()) { + String t = tok.nextToken(); + if (t.startsWith("PROTOCOL=")) + pr = t.substring("PROTOTCOL=".length()); + else if (t.startsWith("FROM_PORT=")) + fp = t.substring("FROM_PORT=".length()); + else if (t.startsWith("TO_PORT=")) + tp = t.substring("TO_PORT=".length()); + } + int proto = I2PSession.PROTO_UNSPECIFIED; + int fromPort = I2PSession.PORT_UNSPECIFIED; + int toPort = I2PSession.PORT_UNSPECIFIED; + if (pr != null) { + try { + proto = Integer.parseInt(pr); + } catch (NumberFormatException nfe) { + warn("Bad datagram header received"); + return; + } + } + if (fp != null) { + try { + fromPort = Integer.parseInt(fp); + } catch (NumberFormatException nfe) { + warn("Bad datagram header received"); + return; + } + } + if (tp != null) { + try { + toPort = Integer.parseInt(tp); + } catch (NumberFormatException nfe) { + warn("Bad datagram header received"); + return; + } + } + // TODO too many allocations and copies. One here and one in Listener above. + byte[] data = new byte[is.available()]; + is.read(data); + SAMv3Handler.Session sess = rec.getHandler().getSession(); + if (sess != null) + rec.getHandler().getSession().sendBytes(dest,data, proto, fromPort, toPort); + else + warn("Dropping datagram, no session for " + nick); + } else { + warn("Dropping datagram, no session for " + nick); + } + } catch (Exception e) { + warn("Error handling datagram", e); + } + } + + /** @since 0.9.22 */ + private static void warn(String s) { + warn(s, null); + } + + /** @since 0.9.22 */ + private static void warn(String s, Throwable t) { + Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3DatagramServer.class); + if (log.shouldLog(Log.WARN)) + log.warn(s, t); + } + } +} diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java index 268083b63..e59ba367c 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; class SAMv3DatagramSession extends SAMDatagramSession implements SAMv3Handler.Session, SAMDatagramReceiver { private final SAMv3Handler handler; - private final SAMv3Handler.DatagramServer server; + private final SAMv3DatagramServer server; private final String nick; private final SocketAddress clientAddress; @@ -44,7 +44,7 @@ class SAMv3DatagramSession extends SAMDatagramSession implements SAMv3Handler.Se ); this.nick = nick ; this.recv = this ; // replacement - this.server = SAMv3Handler.DatagramServer.getInstance() ; + this.server = SAMv3DatagramServer.getInstance() ; SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); if ( rec==null ) throw new SAMException("Record disappeared for nickname : \""+nick+"\"") ; diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index eb5ff9f64..1666ece03 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -10,7 +10,6 @@ package net.i2p.sam; import java.io.ByteArrayOutputStream; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; @@ -18,7 +17,6 @@ import java.net.ConnectException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.NoRouteToHostException; -import java.nio.channels.DatagramChannel; import java.nio.channels.SocketChannel; import java.nio.ByteBuffer; import java.util.Properties; @@ -101,175 +99,6 @@ class SAMv3Handler extends SAMv1Handler { return (verMajor == 3); } - - public static class DatagramServer { - - private static DatagramServer _instance; - private static DatagramChannel server; - - public static DatagramServer getInstance() throws IOException { - return getInstance(new Properties()); - } - - public static DatagramServer getInstance(Properties props) throws IOException { - synchronized(DatagramServer.class) { - if (_instance==null) - _instance = new DatagramServer(props); - return _instance ; - } - } - - public DatagramServer(Properties props) throws IOException { - synchronized(DatagramServer.class) { - if (server==null) - server = DatagramChannel.open(); - } - - String host = props.getProperty(SAMBridge.PROP_DATAGRAM_HOST, SAMBridge.DEFAULT_DATAGRAM_HOST); - String portStr = props.getProperty(SAMBridge.PROP_DATAGRAM_PORT, SAMBridge.DEFAULT_DATAGRAM_PORT); - int port ; - try { - port = Integer.parseInt(portStr); - } catch (NumberFormatException e) { - port = Integer.parseInt(SAMBridge.DEFAULT_DATAGRAM_PORT); - } - - server.socket().bind(new InetSocketAddress(host, port)); - new I2PAppThread(new Listener(server), "DatagramListener").start(); - } - - public void send(SocketAddress addr, ByteBuffer msg) throws IOException { - server.send(msg, addr); - } - - static class Listener implements Runnable { - - private final DatagramChannel server; - - public Listener(DatagramChannel server) - { - this.server = server ; - } - public void run() - { - ByteBuffer inBuf = ByteBuffer.allocateDirect(SAMRawSession.RAW_SIZE_MAX+1024); - - while (!Thread.interrupted()) - { - inBuf.clear(); - try { - server.receive(inBuf); - } catch (IOException e) { - break ; - } - inBuf.flip(); - ByteBuffer outBuf = ByteBuffer.wrap(new byte[inBuf.remaining()]); - outBuf.put(inBuf); - outBuf.flip(); - // A new thread for every message is wildly inefficient... - //new I2PAppThread(new MessageDispatcher(outBuf.array()), "MessageDispatcher").start(); - // inline - // Even though we could be sending messages through multiple sessions, - // that isn't a common use case, and blocking should be rare. - // Inside router context, I2CP drops on overflow. - (new MessageDispatcher(outBuf.array())).run(); - } - } - } - } - - private static class MessageDispatcher implements Runnable - { - private final ByteArrayInputStream is; - - public MessageDispatcher(byte[] buf) - { - this.is = new java.io.ByteArrayInputStream(buf) ; - } - - public void run() { - try { - String header = DataHelper.readLine(is).trim(); - StringTokenizer tok = new StringTokenizer(header, " "); - if (tok.countTokens() < 3) { - // This is not a correct message, for sure - warn("Bad datagram header received"); - return; - } - String version = tok.nextToken(); - if (!version.startsWith("3.")) { - warn("Bad datagram header received"); - return; - } - String nick = tok.nextToken(); - String dest = tok.nextToken(); - - SessionRecord rec = sSessionsHash.get(nick); - if (rec!=null) { - Properties sprops = rec.getProps(); - String pr = sprops.getProperty("PROTOCOL"); - String fp = sprops.getProperty("FROM_PORT"); - String tp = sprops.getProperty("TO_PORT"); - while (tok.hasMoreTokens()) { - String t = tok.nextToken(); - if (t.startsWith("PROTOCOL=")) - pr = t.substring("PROTOTCOL=".length()); - else if (t.startsWith("FROM_PORT=")) - fp = t.substring("FROM_PORT=".length()); - else if (t.startsWith("TO_PORT=")) - tp = t.substring("TO_PORT=".length()); - } - int proto = I2PSession.PROTO_UNSPECIFIED; - int fromPort = I2PSession.PORT_UNSPECIFIED; - int toPort = I2PSession.PORT_UNSPECIFIED; - if (pr != null) { - try { - proto = Integer.parseInt(pr); - } catch (NumberFormatException nfe) { - warn("Bad datagram header received"); - return; - } - } - if (fp != null) { - try { - fromPort = Integer.parseInt(fp); - } catch (NumberFormatException nfe) { - warn("Bad datagram header received"); - return; - } - } - if (tp != null) { - try { - toPort = Integer.parseInt(tp); - } catch (NumberFormatException nfe) { - warn("Bad datagram header received"); - return; - } - } - // TODO too many allocations and copies. One here and one in Listener above. - byte[] data = new byte[is.available()]; - is.read(data); - rec.getHandler().session.sendBytes(dest,data, proto, fromPort, toPort); - } else { - warn("Dropping datagram, no session for " + nick); - } - } catch (Exception e) { - warn("Error handling datagram", e); - } - } - - /** @since 0.9.22 */ - private static void warn(String s) { - warn(s, null); - } - - /** @since 0.9.22 */ - private static void warn(String s, Throwable t) { - Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3Handler.class); - if (log.shouldLog(Log.WARN)) - log.warn(s, t); - } - } /** * The values in the SessionsDB @@ -408,6 +237,15 @@ class SAMv3Handler extends SAMv1Handler return bridge; } + /** + * For SAMv3DatagramServer + * @return may be null + * @since 0.9.22 + */ + Session getSession() { + return session; + } + public void handle() { String msg = null; String domain = null; @@ -682,12 +520,12 @@ class SAMv3Handler extends SAMv1Handler // Create the session if (style.equals("RAW")) { - DatagramServer.getInstance(i2cpProps); + SAMv3DatagramServer.getInstance(i2cpProps); SAMv3RawSession v3 = newSAMRawSession(nick); rawSession = v3; this.session = v3; } else if (style.equals("DATAGRAM")) { - DatagramServer.getInstance(i2cpProps); + SAMv3DatagramServer.getInstance(i2cpProps); SAMv3DatagramSession v3 = newSAMDatagramSession(nick); datagramSession = v3; this.session = v3; diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java index 9cee67791..aebcb482b 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java @@ -22,7 +22,7 @@ class SAMv3RawSession extends SAMRawSession implements SAMv3Handler.Session, SA private final String nick; private final SAMv3Handler handler; - private final SAMv3Handler.DatagramServer server; + private final SAMv3DatagramServer server; private final SocketAddress clientAddress; public String getNick() { return nick; } @@ -43,7 +43,7 @@ class SAMv3RawSession extends SAMRawSession implements SAMv3Handler.Session, SA ); this.nick = nick ; this.recv = this ; // replacement - this.server = SAMv3Handler.DatagramServer.getInstance() ; + this.server = SAMv3DatagramServer.getInstance() ; SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); if (rec == null) throw new InterruptedIOException() ; From f56ac66d645c3d24c0e7ac892210c3df73d33e57 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 27 Jun 2015 16:02:15 +0000 Subject: [PATCH 13/46] Make DatagramServer a Handler, register with bridge --- .../src/net/i2p/sam/SAMv3DatagramServer.java | 58 ++++++++++++++++--- .../java/src/net/i2p/sam/SAMv3Handler.java | 4 +- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java index 39a9f9034..f5d76c96d 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java @@ -32,18 +32,21 @@ import net.i2p.util.Log; * * @since 0.9.22 moved from SAMv3Handler */ -class SAMv3DatagramServer { +class SAMv3DatagramServer implements Handler { private static SAMv3DatagramServer _instance; private static DatagramChannel server; + private final Thread _listener; + private final SAMBridge _parent; /** * Returns the singleton. * If this is the first call, will be instantiated and will listen * on the default host:port 127.0.0.1:7655. + * Don't make this the first call. */ public static SAMv3DatagramServer getInstance() throws IOException { - return getInstance(new Properties()); + return getInstance(null, new Properties()); } /** @@ -54,15 +57,24 @@ class SAMv3DatagramServer { * * @param props ignored unless this is the first call */ - public static SAMv3DatagramServer getInstance(Properties props) throws IOException { + public static SAMv3DatagramServer getInstance(SAMBridge parent, Properties props) throws IOException { synchronized(SAMv3DatagramServer.class) { - if (_instance==null) - _instance = new SAMv3DatagramServer(props); - return _instance ; + if (_instance==null) { + _instance = new SAMv3DatagramServer(parent, props); + _instance.start(); + } } + return _instance; } - private SAMv3DatagramServer(Properties props) throws IOException { + /** + * Does not start listener. + * Caller must call start(). + * + * @param parent may be null + */ + private SAMv3DatagramServer(SAMBridge parent, Properties props) throws IOException { + _parent = parent; synchronized(SAMv3DatagramServer.class) { if (server==null) server = DatagramChannel.open(); @@ -78,13 +90,41 @@ class SAMv3DatagramServer { } server.socket().bind(new InetSocketAddress(host, port)); - new I2PAppThread(new Listener(server), "DatagramListener").start(); + _listener = new I2PAppThread(new Listener(server), "SAM DatagramListener " + port); + } + + /** + * Only call once. + * @since 0.9.22 + */ + public void start() { + _listener.start(); + if (_parent != null) + _parent.register(this); + } + + /** + * Cannot be restarted. + * @since 0.9.22 + */ + public void stopHandling() { + synchronized(SAMv3DatagramServer.class) { + if (server != null) { + try { + server.close(); + } catch (IOException ioe) {} + server = null; + } + } + _listener.interrupt(); + if (_parent != null) + _parent.unregister(this); } public void send(SocketAddress addr, ByteBuffer msg) throws IOException { server.send(msg, addr); } - + private static class Listener implements Runnable { private final DatagramChannel server; diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index 1666ece03..5505eb286 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -520,12 +520,12 @@ class SAMv3Handler extends SAMv1Handler // Create the session if (style.equals("RAW")) { - SAMv3DatagramServer.getInstance(i2cpProps); + SAMv3DatagramServer.getInstance(bridge, i2cpProps); SAMv3RawSession v3 = newSAMRawSession(nick); rawSession = v3; this.session = v3; } else if (style.equals("DATAGRAM")) { - SAMv3DatagramServer.getInstance(i2cpProps); + SAMv3DatagramServer.getInstance(bridge, i2cpProps); SAMv3DatagramSession v3 = newSAMDatagramSession(nick); datagramSession = v3; this.session = v3; From 072e4dc2bfd8fe8eb9f859abc90678abc9a634a4 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 27 Jun 2015 19:46:45 +0000 Subject: [PATCH 14/46] Add ReadLine with timeouts Implement PING Handle QUIT, STOP, EXIT synch DatagramServer start/stop --- apps/sam/java/src/net/i2p/sam/ReadLine.java | 70 +++++++++++++ .../src/net/i2p/sam/SAMv3DatagramServer.java | 4 +- .../java/src/net/i2p/sam/SAMv3Handler.java | 99 ++++++++++++++++++- 3 files changed, 167 insertions(+), 6 deletions(-) create mode 100644 apps/sam/java/src/net/i2p/sam/ReadLine.java diff --git a/apps/sam/java/src/net/i2p/sam/ReadLine.java b/apps/sam/java/src/net/i2p/sam/ReadLine.java new file mode 100644 index 000000000..889fc6c31 --- /dev/null +++ b/apps/sam/java/src/net/i2p/sam/ReadLine.java @@ -0,0 +1,70 @@ +package net.i2p.sam; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.Socket; +import java.net.SocketException; +import java.net.SocketTimeoutException; + +/** + * Modified from I2PTunnelHTTPServer + * + * @since 0.9.22 + */ +class ReadLine { + + private static final int MAX_LINE_LENGTH = 8*1024; + + /** + * Read a line teriminated by newline, with a total read timeout. + * + * Warning - strips \n but not \r + * Warning - 8KB line length limit as of 0.7.13, @throws IOException if exceeded + * + * @param buf output + * @param timeout throws SocketTimeoutException immediately if zero or negative + * @throws SocketTimeoutException if timeout is reached before newline + * @throws EOFException if EOF is reached before newline + * @throws LineTooLongException if too long + * @throws IOException on other errors in the underlying stream + */ + public static void readLine(Socket socket, StringBuilder buf, int timeout) throws IOException { + if (timeout <= 0) + throw new SocketTimeoutException(); + long expires = System.currentTimeMillis() + timeout; + InputStreamReader in = new InputStreamReader(socket.getInputStream(), "UTF-8"); + int c; + int i = 0; + socket.setSoTimeout(timeout); + while ( (c = in.read()) != -1) { + if (++i > MAX_LINE_LENGTH) + throw new LineTooLongException("Line too long - max " + MAX_LINE_LENGTH); + if (c == '\n') + break; + int newTimeout = (int) (expires - System.currentTimeMillis()); + if (newTimeout <= 0) + throw new SocketTimeoutException(); + buf.append((char)c); + if (newTimeout != timeout) { + timeout = newTimeout; + socket.setSoTimeout(timeout); + } + } + if (c == -1) { + if (System.currentTimeMillis() >= expires) + throw new SocketTimeoutException(); + else + throw new EOFException(); + } + } + + private static class LineTooLongException extends IOException { + public LineTooLongException(String s) { + super(s); + } + } +} + + diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java index f5d76c96d..94268824c 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java @@ -97,7 +97,7 @@ class SAMv3DatagramServer implements Handler { * Only call once. * @since 0.9.22 */ - public void start() { + private synchronized void start() { _listener.start(); if (_parent != null) _parent.register(this); @@ -107,7 +107,7 @@ class SAMv3DatagramServer implements Handler { * Cannot be restarted. * @since 0.9.22 */ - public void stopHandling() { + public synchronized void stopHandling() { synchronized(SAMv3DatagramServer.class) { if (server != null) { try { diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index 5505eb286..7b073d2e2 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -15,7 +15,10 @@ import java.io.InputStream; import java.io.InterruptedIOException; import java.net.ConnectException; import java.net.InetSocketAddress; +import java.net.Socket; import java.net.SocketAddress; +import java.net.SocketException; +import java.net.SocketTimeoutException; import java.net.NoRouteToHostException; import java.nio.channels.SocketChannel; import java.nio.ByteBuffer; @@ -51,6 +54,8 @@ class SAMv3Handler extends SAMv1Handler private volatile boolean stolenSocket; private volatile boolean streamForwardingSocket; private final boolean sendPorts; + private long _lastPing; + private static final int READ_TIMEOUT = 3*60*1000; interface Session { String getNick(); @@ -226,6 +231,11 @@ class SAMv3Handler extends SAMv1Handler public void stealSocket() { stolenSocket = true ; + if (sendPorts) { + try { + socket.socket().setSoTimeout(0); + } catch (SocketException se) {} + } this.stopHandling(); } @@ -246,6 +256,7 @@ class SAMv3Handler extends SAMv1Handler return session; } + @Override public void handle() { String msg = null; String domain = null; @@ -259,15 +270,72 @@ class SAMv3Handler extends SAMv1Handler _log.debug("SAMv3 handling started"); try { - InputStream in = getClientSocket().socket().getInputStream(); + Socket socket = getClientSocket().socket(); + InputStream in = socket.getInputStream(); + StringBuilder buf = new StringBuilder(1024); while (true) { if (shouldStop()) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Stop request found"); break; } - String line = DataHelper.readLine(in) ; + String line; + if (sendPorts) { + // client supports PING + try { + ReadLine.readLine(socket, buf, READ_TIMEOUT); + line = buf.toString(); + buf.setLength(0); + } catch (SocketTimeoutException ste) { + long now = System.currentTimeMillis(); + if (buf.length() <= 0) { + if (_lastPing > 0) { + if (now - _lastPing >= READ_TIMEOUT) { + if (_log.shouldWarn()) + _log.warn("Failed to respond to PING"); + writeString("PING STATUS RESULT=I2P_ERROR MESSAGE=\"PONG timeout\"\n"); + break; + } + } else { + if (_log.shouldDebug()) + _log.debug("Sendng PING " + now); + _lastPing = now; + if (!writeString("PING " + now + '\n')) + break; + } + } else { + if (_lastPing > 0) { + if (now - _lastPing >= 2*READ_TIMEOUT) { + if (_log.shouldWarn()) + _log.warn("Failed to respond to PING"); + writeString("PING STATUS RESULT=I2P_ERROR MESSAGE=\"PONG timeout\"\n"); + break; + } + } else if (_lastPing < 0) { + if (_log.shouldWarn()) + _log.warn("2nd timeout"); + writeString("XXX STATUS RESULT=I2P_ERROR MESSAGE=\"command timeout, bye\"\n"); + break; + } else { + // don't clear buffer, don't send ping, + // go around again + _lastPing = -1; + if (_log.shouldWarn()) + _log.warn("timeout after partial: " + buf); + } + } + if (_log.shouldDebug()) + _log.debug("loop after timeout"); + continue; + } + } else { + buf.setLength(0); + if (DataHelper.readLine(in, buf)) + line = buf.toString(); + else + line = null; + } if (line==null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Connection closed by client (line read : null)"); @@ -302,6 +370,10 @@ class SAMv3Handler extends SAMv1Handler } else if (domain.equals("PONG")) { execPongMessage(tok); continue; + } else if (domain.equals("QUIT") || domain.equals("STOP") || + domain.equals("EXIT")) { + writeString(domain + " STATUS RESULT=OK MESSAGE=bye\n"); + break; } if (count <= 1) { // This is not a correct message, for sure @@ -345,7 +417,7 @@ class SAMv3Handler extends SAMv1Handler if (!canContinue) { break; } - } + } // while } catch (IOException e) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Caught IOException for message [" + msg + "]", e); @@ -855,7 +927,26 @@ class SAMv3Handler extends SAMv1Handler * @since 0.9.22 */ private void execPongMessage(StringTokenizer tok) { - // TODO. We don't send PINGs yet. + String s; + if (tok.hasMoreTokens()) { + s = tok.nextToken(); + } else { + s = ""; + } + if (_lastPing > 0) { + String expected = Long.toString(_lastPing); + if (expected.equals(s)) { + _lastPing = 0; + if (_log.shouldInfo()) + _log.warn("Got expected pong: " + s); + } else { + if (_log.shouldInfo()) + _log.warn("Got unexpected pong: " + s); + } + } else { + if (_log.shouldWarn()) + _log.warn("Pong received without a ping: " + s); + } } } From 6e06d326e3cc15a85ff898f7281cd2e3a8a799c1 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 27 Jun 2015 20:31:56 +0000 Subject: [PATCH 15/46] Use ReadLine for SAMHandlerFactory --- apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java index fbe1e905a..4bb374f8d 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java +++ b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java @@ -46,14 +46,11 @@ class SAMHandlerFactory { try { Socket sock = s.socket(); - sock.setSoTimeout(HELLO_TIMEOUT); sock.setKeepAlive(true); - String line = DataHelper.readLine(sock.getInputStream()); + StringBuilder buf = new StringBuilder(128); + ReadLine.readLine(sock, buf, HELLO_TIMEOUT); + String line = buf.toString(); sock.setSoTimeout(0); - if (line == null) { - log.debug("Connection closed by client"); - return null; - } tok = new StringTokenizer(line.trim(), " "); } catch (SocketTimeoutException e) { throw new SAMException("Timeout waiting for HELLO VERSION", e); From 8b14afd605c504673fb13994353de270593b6cb1 Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 20 Jul 2015 14:44:22 +0000 Subject: [PATCH 16/46] Add SSLSocketChannel wrappers after review Requires Java 7 to compile --- .../net/i2p/sam/SSLServerSocketChannel.java | 77 ++++++++++ .../src/net/i2p/sam/SSLSocketChannel.java | 140 ++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 apps/sam/java/src/net/i2p/sam/SSLServerSocketChannel.java create mode 100644 apps/sam/java/src/net/i2p/sam/SSLSocketChannel.java diff --git a/apps/sam/java/src/net/i2p/sam/SSLServerSocketChannel.java b/apps/sam/java/src/net/i2p/sam/SSLServerSocketChannel.java new file mode 100644 index 000000000..f92f43e89 --- /dev/null +++ b/apps/sam/java/src/net/i2p/sam/SSLServerSocketChannel.java @@ -0,0 +1,77 @@ +package net.i2p.sam; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.SocketAddress; +/* requires Java 7 */ +import java.net.SocketOption; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.spi.SelectorProvider; +import java.util.Collections; +import java.util.Set; + +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSocket; + +/** + * Simple wrapper for a SSLServerSocket. + * Cannot be used for asynch ops. + * + * @since 0.9.22 + */ +class SSLServerSocketChannel extends ServerSocketChannel { + + private final SSLServerSocket _socket; + + public SSLServerSocketChannel(SSLServerSocket socket) { + super(SelectorProvider.provider()); + _socket = socket; + } + + //// ServerSocketChannel abstract methods + + public SocketChannel accept() throws IOException { + return new SSLSocketChannel((SSLSocket)_socket.accept()); + } + + public ServerSocket socket() { + return _socket; + } + + /** requires Java 7 */ + public ServerSocketChannel bind(SocketAddress local, int backlog) { + throw new UnsupportedOperationException(); + } + + /** requires Java 7 */ + public ServerSocketChannel setOption(SocketOption name, T value) { + return this; + } + + //// AbstractSelectableChannel abstract methods + + public void implCloseSelectableChannel() throws IOException { + _socket.close(); + } + + public void implConfigureBlocking(boolean block) throws IOException { + if (!block) + throw new UnsupportedOperationException(); + } + + //// NetworkChannel interface methods + + public SocketAddress getLocalAddress() { + return _socket.getLocalSocketAddress(); + } + + public T getOption(SocketOption name) { + return null; + } + + public Set> supportedOptions() { + return Collections.emptySet(); + } +} diff --git a/apps/sam/java/src/net/i2p/sam/SSLSocketChannel.java b/apps/sam/java/src/net/i2p/sam/SSLSocketChannel.java new file mode 100644 index 000000000..9530b396d --- /dev/null +++ b/apps/sam/java/src/net/i2p/sam/SSLSocketChannel.java @@ -0,0 +1,140 @@ +package net.i2p.sam; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +/* requires Java 7 */ +import java.net.SocketOption; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; +import java.util.Collections; +import java.util.Set; + +import javax.net.ssl.SSLSocket; + +/** + * Simple wrapper for a SSLSocket. + * Cannot be used for asynch ops. + * + * @since 0.9.22 + */ +class SSLSocketChannel extends SocketChannel { + + private final SSLSocket _socket; + + public SSLSocketChannel(SSLSocket socket) { + super(SelectorProvider.provider()); + _socket = socket; + } + + //// SocketChannel abstract methods + + public Socket socket() { + return _socket; + } + + public boolean connect(SocketAddress remote) { + throw new UnsupportedOperationException(); + } + + public boolean finishConnect() { + return true; + } + + public boolean isConnected() { + return _socket.isConnected(); + } + + public boolean isConnectionPending() { + return false; + } + + /** new in Java 7 */ + public SocketAddress getRemoteAddress() { + return _socket.getRemoteSocketAddress(); + } + + /** new in Java 7 */ + public SocketChannel shutdownInput() throws IOException { + _socket.getInputStream().close(); + return this; + } + + /** new in Java 7 */ + public SocketChannel shutdownOutput() throws IOException { + _socket.getOutputStream().close(); + return this; + } + + /** requires Java 7 */ + public SocketChannel setOption(SocketOption name, T value) { + return this; + } + + /** requires Java 7 */ + public SocketChannel bind(SocketAddress local) { + throw new UnsupportedOperationException(); + } + + //// SocketChannel abstract methods + + public int read(ByteBuffer src) throws IOException { + if (!src.hasArray()) + throw new UnsupportedOperationException(); + int pos = src.position(); + int len = src.remaining(); + int read = _socket.getInputStream().read(src.array(), src.arrayOffset() + pos, len); + if (read > 0) + src.position(pos + read); + return read; + } + + public long read(ByteBuffer[] srcs, int offset, int length) { + throw new UnsupportedOperationException(); + } + + public int write(ByteBuffer src) throws IOException { + if (!src.hasArray()) + throw new UnsupportedOperationException(); + int pos = src.position(); + int len = src.remaining(); + _socket.getOutputStream().write(src.array(), src.arrayOffset() + pos, len); + src.position(pos + len); + return len; + } + + public long write(ByteBuffer[] srcs, int offset, int length) { + throw new UnsupportedOperationException(); + } + + //// AbstractSelectableChannel abstract methods + + public void implCloseSelectableChannel() throws IOException { + _socket.close(); + } + + public void implConfigureBlocking(boolean block) throws IOException { + if (!block) + throw new UnsupportedOperationException(); + } + + + //// NetworkChannel interface methods + + public SocketAddress getLocalAddress() { + return _socket.getLocalSocketAddress(); + } + + public T getOption(SocketOption name) { + return null; + } + + public Set> supportedOptions() { + return Collections.emptySet(); + } + + + +} From c7d68c2a6cea3b629f6373134a2f401403b755cf Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 19 Nov 2015 17:48:56 +0000 Subject: [PATCH 17/46] Require Java 7 for SAM separate option javac.compilerargs7 for Java 7 --- apps/sam/java/build.xml | 12 +++++++----- build.properties | 3 +++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/sam/java/build.xml b/apps/sam/java/build.xml index 5d93ae476..9ecc58b8c 100644 --- a/apps/sam/java/build.xml +++ b/apps/sam/java/build.xml @@ -21,7 +21,9 @@ - + + + @@ -30,22 +32,22 @@ - + - + diff --git a/build.properties b/build.properties index 195a13c6b..897e3c24e 100644 --- a/build.properties +++ b/build.properties @@ -43,8 +43,11 @@ javac.version=1.6 #javac.classpath=/PATH/TO/pack200.jar # Optional compiler args +# This one is for subsystems requiring Java 6 # This one keeps gcj a lot quieter #javac.compilerargs=-warn:-unchecked,raw,unused,serial +# This one is for subsystems requiring Java 7 +#javac.compilerargs7= # # Note to packagers, embedders, distributors: From 110a0a1b7a4c349587b93131e45d3b683fb043b2 Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 23 Nov 2015 18:19:17 +0000 Subject: [PATCH 18/46] Remove singleton SAMv3DatagramServer; hang off of SAMBridge SAMv3DatagramSession whitespace fixes @since change to 0.9.24 --- apps/sam/java/src/net/i2p/sam/ReadLine.java | 2 +- apps/sam/java/src/net/i2p/sam/SAMBridge.java | 41 +++++++++- .../src/net/i2p/sam/SAMMessageSession.java | 2 +- .../src/net/i2p/sam/SAMv3DatagramServer.java | 81 ++++++------------- .../src/net/i2p/sam/SAMv3DatagramSession.java | 51 ++++++------ .../java/src/net/i2p/sam/SAMv3Handler.java | 30 +++---- .../java/src/net/i2p/sam/SAMv3RawSession.java | 6 +- .../net/i2p/sam/SSLServerSocketChannel.java | 2 +- .../src/net/i2p/sam/SSLSocketChannel.java | 2 +- apps/sam/java/src/net/i2p/sam/SSLUtil.java | 2 +- 10 files changed, 105 insertions(+), 114 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/ReadLine.java b/apps/sam/java/src/net/i2p/sam/ReadLine.java index 889fc6c31..94cf708df 100644 --- a/apps/sam/java/src/net/i2p/sam/ReadLine.java +++ b/apps/sam/java/src/net/i2p/sam/ReadLine.java @@ -11,7 +11,7 @@ import java.net.SocketTimeoutException; /** * Modified from I2PTunnelHTTPServer * - * @since 0.9.22 + * @since 0.9.24 */ class ReadLine { diff --git a/apps/sam/java/src/net/i2p/sam/SAMBridge.java b/apps/sam/java/src/net/i2p/sam/SAMBridge.java index 4b9a3293c..905ae808d 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMBridge.java +++ b/apps/sam/java/src/net/i2p/sam/SAMBridge.java @@ -60,6 +60,8 @@ public class SAMBridge implements Runnable, ClientApp { private final boolean _useSSL; private final File _configFile; private volatile Thread _runner; + private final Object _v3DGServerLock = new Object(); + private SAMv3DatagramServer _v3DGServer; /** * filename in which the name to private key mapping should @@ -95,7 +97,8 @@ public class SAMBridge implements Runnable, ClientApp { public static final String PROP_DATAGRAM_HOST = "sam.udp.host"; public static final String PROP_DATAGRAM_PORT = "sam.udp.port"; protected static final String DEFAULT_DATAGRAM_HOST = "127.0.0.1"; - protected static final String DEFAULT_DATAGRAM_PORT = "7655"; + protected static final int DEFAULT_DATAGRAM_PORT_INT = 7655; + protected static final String DEFAULT_DATAGRAM_PORT = Integer.toString(DEFAULT_DATAGRAM_PORT_INT); /** @@ -354,6 +357,40 @@ public class SAMBridge implements Runnable, ClientApp { } } + /** + * Was a static singleton, now a singleton for this bridge. + * Instantiate and start server if it doesn't exist. + * We only listen on one host and port, as specified in the + * sam.udp.host and sam.udp.port properties. + * TODO we could have multiple servers on different hosts/ports in the future. + * + * @param props non-null instantiate and start server if it doesn't exist + * @param return non-null + * @throws IOException if can't bind to host/port, or if different than existing + * @since 0.9.24 + */ + SAMv3DatagramServer getV3DatagramServer(Properties props) throws IOException { + String host = props.getProperty(PROP_DATAGRAM_HOST, DEFAULT_DATAGRAM_HOST); + int port; + String portStr = props.getProperty(PROP_DATAGRAM_PORT, DEFAULT_DATAGRAM_PORT); + try { + port = Integer.parseInt(portStr); + } catch (NumberFormatException e) { + port = DEFAULT_DATAGRAM_PORT_INT; + } + synchronized (_v3DGServerLock) { + if (_v3DGServer == null) { + _v3DGServer = new SAMv3DatagramServer(this, host, port, props); + _v3DGServer.start(); + } else { + if (_v3DGServer.getPort() != port || !_v3DGServer.getHost().equals(host)) + throw new IOException("Already have V3 DatagramServer with host=" + host + " port=" + port); + } + return _v3DGServer; + } + } + + ////// begin ClientApp interface, use only if using correct construtor /** @@ -750,7 +787,7 @@ public class SAMBridge implements Runnable, ClientApp { } } - /** @since 0.9.22 */ + /** @since 0.9.24 */ public void saveConfig() throws IOException { DataHelper.storeProps(i2cpProps, _configFile); } diff --git a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java index 8613ed2d0..a2ab4a712 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java @@ -249,7 +249,7 @@ abstract class SAMMessageSession { I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED); } - /** @since 0.9.22 */ + /** @since 0.9.24 */ public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromPort, int toPort) { diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java index 94268824c..b410fb21b 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java @@ -26,78 +26,42 @@ import net.i2p.util.I2PAppThread; import net.i2p.util.Log; /** - * This is a singleton listening on 127.0.0.1:7655 or as specified by + * This is the thread listening on 127.0.0.1:7655 or as specified by * sam.udp.host and sam.udp.port properties. * This is used for both repliable and raw datagrams. * - * @since 0.9.22 moved from SAMv3Handler + * @since 0.9.24 moved from SAMv3Handler */ class SAMv3DatagramServer implements Handler { - private static SAMv3DatagramServer _instance; - private static DatagramChannel server; + private final DatagramChannel _server; private final Thread _listener; private final SAMBridge _parent; - - /** - * Returns the singleton. - * If this is the first call, will be instantiated and will listen - * on the default host:port 127.0.0.1:7655. - * Don't make this the first call. - */ - public static SAMv3DatagramServer getInstance() throws IOException { - return getInstance(null, new Properties()); - } - - /** - * Returns the singleton. - * If this is the first call, will be instantiated and will listen - * on the specified host:port, default 127.0.0.1:7655. - * Properties are sam.udp.host and sam.udp.port. - * - * @param props ignored unless this is the first call - */ - public static SAMv3DatagramServer getInstance(SAMBridge parent, Properties props) throws IOException { - synchronized(SAMv3DatagramServer.class) { - if (_instance==null) { - _instance = new SAMv3DatagramServer(parent, props); - _instance.start(); - } - } - return _instance; - } + private final String _host; + private final int _port; /** * Does not start listener. * Caller must call start(). * * @param parent may be null + * @param props ignored for now */ - private SAMv3DatagramServer(SAMBridge parent, Properties props) throws IOException { + public SAMv3DatagramServer(SAMBridge parent, String host, int port, Properties props) throws IOException { _parent = parent; - synchronized(SAMv3DatagramServer.class) { - if (server==null) - server = DatagramChannel.open(); - } + _server = DatagramChannel.open(); - String host = props.getProperty(SAMBridge.PROP_DATAGRAM_HOST, SAMBridge.DEFAULT_DATAGRAM_HOST); - String portStr = props.getProperty(SAMBridge.PROP_DATAGRAM_PORT, SAMBridge.DEFAULT_DATAGRAM_PORT); - int port ; - try { - port = Integer.parseInt(portStr); - } catch (NumberFormatException e) { - port = Integer.parseInt(SAMBridge.DEFAULT_DATAGRAM_PORT); - } - - server.socket().bind(new InetSocketAddress(host, port)); - _listener = new I2PAppThread(new Listener(server), "SAM DatagramListener " + port); + _server.socket().bind(new InetSocketAddress(host, port)); + _listener = new I2PAppThread(new Listener(_server), "SAM DatagramListener " + port); + _host = host; + _port = port; } /** * Only call once. * @since 0.9.22 */ - private synchronized void start() { + public synchronized void start() { _listener.start(); if (_parent != null) _parent.register(this); @@ -108,23 +72,24 @@ class SAMv3DatagramServer implements Handler { * @since 0.9.22 */ public synchronized void stopHandling() { - synchronized(SAMv3DatagramServer.class) { - if (server != null) { - try { - server.close(); - } catch (IOException ioe) {} - server = null; - } - } + try { + _server.close(); + } catch (IOException ioe) {} _listener.interrupt(); if (_parent != null) _parent.unregister(this); } public void send(SocketAddress addr, ByteBuffer msg) throws IOException { - server.send(msg, addr); + _server.send(msg, addr); } + /** @since 0.9.24 */ + public String getHost() { return _host; } + + /** @since 0.9.24 */ + public int getPort() { return _port; } + private static class Listener implements Runnable { private final DatagramChannel server; diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java index e59ba367c..ee2782559 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java @@ -30,45 +30,44 @@ class SAMv3DatagramSession extends SAMDatagramSession implements SAMv3Handler.Se /** * build a DatagramSession according to informations registered * with the given nickname + * * @param nick nickname of the session * @throws IOException * @throws DataFormatException * @throws I2PSessionException */ - public SAMv3DatagramSession(String nick) - throws IOException, DataFormatException, I2PSessionException, SAMException { - + public SAMv3DatagramSession(String nick, SAMv3DatagramServer dgServer) + throws IOException, DataFormatException, I2PSessionException, SAMException { super(SAMv3Handler.sSessionsHash.get(nick).getDest(), SAMv3Handler.sSessionsHash.get(nick).getProps(), null // to be replaced by this ); - this.nick = nick ; - this.recv = this ; // replacement - this.server = SAMv3DatagramServer.getInstance() ; + this.nick = nick; + this.recv = this; // replacement + this.server = dgServer; SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); - if ( rec==null ) throw new SAMException("Record disappeared for nickname : \""+nick+"\"") ; + if (rec == null) + throw new SAMException("Record disappeared for nickname : \""+nick+"\""); - this.handler = rec.getHandler(); + this.handler = rec.getHandler(); - Properties props = rec.getProps(); - String portStr = props.getProperty("PORT") ; - if ( portStr==null ) { - _log.debug("receiver port not specified. Current socket will be used."); - this.clientAddress = null; - } - else { - int port = Integer.parseInt(portStr); - - String host = props.getProperty("HOST"); - if ( host==null ) { - host = rec.getHandler().getClientIP(); - _log.debug("no host specified. Taken from the client socket : " + host+':'+port); - } - - - this.clientAddress = new InetSocketAddress(host,port); - } + Properties props = rec.getProps(); + String portStr = props.getProperty("PORT"); + if (portStr == null) { + if (_log.shouldDebug()) + _log.debug("receiver port not specified. Current socket will be used."); + this.clientAddress = null; + } else { + int port = Integer.parseInt(portStr); + String host = props.getProperty("HOST"); + if (host == null) { + host = rec.getHandler().getClientIP(); + if (_log.shouldDebug()) + _log.debug("no host specified. Taken from the client socket : " + host+':'+port); + } + this.clientAddress = new InetSocketAddress(host, port); + } } public void receiveDatagramBytes(Destination sender, byte[] data, int proto, diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index 7b073d2e2..246cd5fb9 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -250,7 +250,7 @@ class SAMv3Handler extends SAMv1Handler /** * For SAMv3DatagramServer * @return may be null - * @since 0.9.22 + * @since 0.9.24 */ Session getSession() { return session; @@ -592,13 +592,13 @@ class SAMv3Handler extends SAMv1Handler // Create the session if (style.equals("RAW")) { - SAMv3DatagramServer.getInstance(bridge, i2cpProps); - SAMv3RawSession v3 = newSAMRawSession(nick); + SAMv3DatagramServer dgs = bridge.getV3DatagramServer(props); + SAMv3RawSession v3 = new SAMv3RawSession(nick, dgs); rawSession = v3; this.session = v3; } else if (style.equals("DATAGRAM")) { - SAMv3DatagramServer.getInstance(bridge, i2cpProps); - SAMv3DatagramSession v3 = newSAMDatagramSession(nick); + SAMv3DatagramServer dgs = bridge.getV3DatagramServer(props); + SAMv3DatagramSession v3 = new SAMv3DatagramSession(nick, dgs); datagramSession = v3; this.session = v3; } else if (style.equals("STREAM")) { @@ -652,18 +652,6 @@ class SAMv3Handler extends SAMv1Handler return new SAMv3StreamSession( login ) ; } - private static SAMv3RawSession newSAMRawSession(String login ) - throws IOException, DataFormatException, SAMException, I2PSessionException - { - return new SAMv3RawSession( login ) ; - } - - private static SAMv3DatagramSession newSAMDatagramSession(String login ) - throws IOException, DataFormatException, SAMException, I2PSessionException - { - return new SAMv3DatagramSession( login ) ; - } - /* Parse and execute a STREAM message */ @Override protected boolean execStreamMessage ( String opcode, Properties props ) @@ -863,7 +851,7 @@ class SAMv3Handler extends SAMv1Handler } } - /** @since 0.9.22 */ + /** @since 0.9.24 */ public static void notifyStreamIncomingConnection(SocketChannel client, Destination d, int fromPort, int toPort) throws IOException { if (!writeString(d.toBase64() + " FROM_PORT=" + fromPort + " TO_PORT=" + toPort + '\n', client)) { @@ -871,7 +859,7 @@ class SAMv3Handler extends SAMv1Handler } } - /** @since 0.9.22 */ + /** @since 0.9.24 */ private boolean execAuthMessage(String opcode, Properties props) { if (opcode.equals("ENABLE")) { i2cpProps.setProperty(SAMBridge.PROP_AUTH, "true"); @@ -910,7 +898,7 @@ class SAMv3Handler extends SAMv1Handler /** * Handle a PING. * Send a PONG. - * @since 0.9.22 + * @since 0.9.24 */ private void execPingMessage(StringTokenizer tok) { StringBuilder buf = new StringBuilder(); @@ -924,7 +912,7 @@ class SAMv3Handler extends SAMv1Handler /** * Handle a PONG. - * @since 0.9.22 + * @since 0.9.24 */ private void execPongMessage(StringTokenizer tok) { String s; diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java index aebcb482b..658aaae0a 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java @@ -36,14 +36,16 @@ class SAMv3RawSession extends SAMRawSession implements SAMv3Handler.Session, SA * @throws DataFormatException * @throws I2PSessionException */ - public SAMv3RawSession(String nick) throws IOException, DataFormatException, I2PSessionException { + public SAMv3RawSession(String nick, SAMv3DatagramServer dgServer) + throws IOException, DataFormatException, I2PSessionException { super(SAMv3Handler.sSessionsHash.get(nick).getDest(), SAMv3Handler.sSessionsHash.get(nick).getProps(), SAMv3Handler.sSessionsHash.get(nick).getHandler() // to be replaced by this ); this.nick = nick ; this.recv = this ; // replacement - this.server = SAMv3DatagramServer.getInstance() ; + this.server = dgServer; + SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); if (rec == null) throw new InterruptedIOException() ; diff --git a/apps/sam/java/src/net/i2p/sam/SSLServerSocketChannel.java b/apps/sam/java/src/net/i2p/sam/SSLServerSocketChannel.java index f92f43e89..546955cee 100644 --- a/apps/sam/java/src/net/i2p/sam/SSLServerSocketChannel.java +++ b/apps/sam/java/src/net/i2p/sam/SSLServerSocketChannel.java @@ -19,7 +19,7 @@ import javax.net.ssl.SSLSocket; * Simple wrapper for a SSLServerSocket. * Cannot be used for asynch ops. * - * @since 0.9.22 + * @since 0.9.24 */ class SSLServerSocketChannel extends ServerSocketChannel { diff --git a/apps/sam/java/src/net/i2p/sam/SSLSocketChannel.java b/apps/sam/java/src/net/i2p/sam/SSLSocketChannel.java index 9530b396d..7b956a16e 100644 --- a/apps/sam/java/src/net/i2p/sam/SSLSocketChannel.java +++ b/apps/sam/java/src/net/i2p/sam/SSLSocketChannel.java @@ -18,7 +18,7 @@ import javax.net.ssl.SSLSocket; * Simple wrapper for a SSLSocket. * Cannot be used for asynch ops. * - * @since 0.9.22 + * @since 0.9.24 */ class SSLSocketChannel extends SocketChannel { diff --git a/apps/sam/java/src/net/i2p/sam/SSLUtil.java b/apps/sam/java/src/net/i2p/sam/SSLUtil.java index bc0331417..bf4ebf1e4 100644 --- a/apps/sam/java/src/net/i2p/sam/SSLUtil.java +++ b/apps/sam/java/src/net/i2p/sam/SSLUtil.java @@ -20,7 +20,7 @@ import net.i2p.util.SecureDirectory; /** * Utilities for SAM SSL server sockets. * - * @since 0.9.22 adopted from net.i2p.i2ptunnel.SSLClientUtil + * @since 0.9.24 adopted from net.i2p.i2ptunnel.SSLClientUtil */ class SSLUtil { From 6081856dd1e8efc55defd87aff975ab43ecfe16f Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 25 Nov 2015 14:48:43 +0000 Subject: [PATCH 19/46] client demo cleanup --- .../net/i2p/sam/client/SAMEventHandler.java | 10 +++---- .../src/net/i2p/sam/client/SAMReader.java | 9 +++--- .../src/net/i2p/sam/client/SAMStreamSend.java | 24 +++++++-------- .../src/net/i2p/sam/client/SAMStreamSink.java | 29 ++++++++++--------- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java b/apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java index 2d1a5b63b..302dbd629 100644 --- a/apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java +++ b/apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java @@ -13,13 +13,13 @@ import net.i2p.util.Log; */ public class SAMEventHandler extends SAMClientEventListenerImpl { //private I2PAppContext _context; - private Log _log; + private final Log _log; private Boolean _helloOk; - private Object _helloLock = new Object(); + private final Object _helloLock = new Object(); private Boolean _sessionCreateOk; - private Object _sessionCreateLock = new Object(); - private Object _namingReplyLock = new Object(); - private Map _namingReplies = new HashMap(); + private final Object _sessionCreateLock = new Object(); + private final Object _namingReplyLock = new Object(); + private final Map _namingReplies = new HashMap(); public SAMEventHandler(I2PAppContext ctx) { //_context = ctx; diff --git a/apps/sam/java/src/net/i2p/sam/client/SAMReader.java b/apps/sam/java/src/net/i2p/sam/client/SAMReader.java index 0421a6458..d25d1f053 100644 --- a/apps/sam/java/src/net/i2p/sam/client/SAMReader.java +++ b/apps/sam/java/src/net/i2p/sam/client/SAMReader.java @@ -16,10 +16,10 @@ import net.i2p.util.Log; * */ public class SAMReader { - private Log _log; - private InputStream _inRaw; - private SAMClientEventListener _listener; - private boolean _live; + private final Log _log; + private final InputStream _inRaw; + private final SAMClientEventListener _listener; + private volatile boolean _live; public SAMReader(I2PAppContext context, InputStream samIn, SAMClientEventListener listener) { _log = context.logManager().getLog(SAMReader.class); @@ -32,6 +32,7 @@ public class SAMReader { I2PAppThread t = new I2PAppThread(new Runner(), "SAM reader"); t.start(); } + public void stopReading() { _live = false; } /** diff --git a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java index a0d82d777..29e040394 100644 --- a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java +++ b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java @@ -20,21 +20,21 @@ import net.i2p.util.Log; * */ public class SAMStreamSend { - private I2PAppContext _context; - private Log _log; - private String _samHost; - private String _samPort; - private String _destFile; - private String _dataFile; - private String _conOptions; + private final I2PAppContext _context; + private final Log _log; + private final String _samHost; + private final String _samPort; + private final String _destFile; + private final String _dataFile; + private final String _conOptions; private Socket _samSocket; private OutputStream _samOut; private InputStream _samIn; private SAMReader _reader; //private boolean _dead; - private SAMEventHandler _eventHandler; + private final SAMEventHandler _eventHandler; /** Connection id (Integer) to peer (Flooder) */ - private Map _remotePeers; + private final Map _remotePeers; public static void main(String args[]) { if (args.length < 4) { @@ -165,13 +165,11 @@ public class SAMStreamSend { private int _connectionId; private String _remoteDestination; private InputStream _in; - private boolean _closed; + private volatile boolean _closed; private long _started; private long _totalSent; - public Sender() { - _closed = false; - } + public Sender() {} public boolean openConnection() { FileInputStream fin = null; diff --git a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java index 666b7116d..0e38ed0c4 100644 --- a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java +++ b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java @@ -20,21 +20,21 @@ import net.i2p.util.Log; * */ public class SAMStreamSink { - private I2PAppContext _context; - private Log _log; - private String _samHost; - private String _samPort; - private String _destFile; - private String _sinkDir; - private String _conOptions; + private final I2PAppContext _context; + private final Log _log; + private final String _samHost; + private final String _samPort; + private final String _destFile; + private final String _sinkDir; + private final String _conOptions; private Socket _samSocket; private OutputStream _samOut; private InputStream _samIn; private SAMReader _reader; //private boolean _dead; - private SAMEventHandler _eventHandler; + private final SAMEventHandler _eventHandler; /** Connection id (Integer) to peer (Flooder) */ - private Map _remotePeers; + private final Map _remotePeers; public static void main(String args[]) { if (args.length < 4) { @@ -200,12 +200,12 @@ public class SAMStreamSink { } private class Sink { - private int _connectionId; - private String _remoteDestination; - private boolean _closed; - private long _started; + private final int _connectionId; + private final String _remoteDestination; + private volatile boolean _closed; + private final long _started; private long _lastReceivedOn; - private OutputStream _out; + private final OutputStream _out; public Sink(int conId, String remDest) throws IOException { _connectionId = conId; @@ -222,6 +222,7 @@ public class SAMStreamSink { File out = File.createTempFile("sink", ".dat", sinkDir); _out = new FileOutputStream(out); + _started = _context.clock().now(); } public int getConnectionId() { return _connectionId; } From 5d07294cc639c69a8eb9be52eafeb67add4b387c Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 25 Nov 2015 15:12:54 +0000 Subject: [PATCH 20/46] require Java 7 in installer --- installer/install.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer/install.xml b/installer/install.xml index cdbf0a510..2513b80bc 100644 --- a/installer/install.xml +++ b/installer/install.xml @@ -9,7 +9,7 @@ https://geti2p.net/ - 1.6 + 1.7