diff --git a/.tx/config b/.tx/config index 9d62a923f..97fd1e1d2 100644 --- a/.tx/config +++ b/.tx/config @@ -404,7 +404,11 @@ trans.fr = installer/resources/locale-man/man_fr.po trans.it = installer/resources/locale-man/man_it.po trans.ko = installer/resources/locale-man/man_ko.po trans.nl = installer/resources/locale-man/man_nl.po +trans.pl = installer/resources/locale-man/man_pl.po trans.pt = installer/resources/locale-man/man_pt.po +trans.pt_BR = installer/resources/locale-man/man_pt_BR.po +trans.ru_RU = installer/resources/locale-man/man_ru.po +trans.sv_SE = installer/resources/locale-man/man_sv.po trans.zh_CN = installer/resources/locale-man/man_zh.po [main] diff --git a/LICENSE.txt b/LICENSE.txt index e0cb65de1..f2ff140ea 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -212,12 +212,11 @@ Applications: Zxing 3.3.0: See licenses/LICENSE-Apache2.0.txt - Jetty 8.1.21.v20160908: + Jetty 9.2.21.v20170120: See licenses/ABOUT-Jetty.html See licenses/NOTICE-Jetty.html See licenses/LICENSE-Apache2.0.txt See licenses/LICENSE-ECLIPSE-1.0.html - See licenses/NOTICE-Commons-Logging.txt JRobin 1.6.0-1: Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor. @@ -284,7 +283,7 @@ Applications: Bundles systray4j-2.4.1: See licenses/LICENSE-LGPLv2.1.txt - Tomcat 6.0.48: + Tomcat 8.0.33: Copyright 1999-2016 The Apache Software Foundation See licenses/LICENSE-Apache2.0.txt See licenses/NOTICE-Tomcat.txt diff --git a/apps/addressbook/build.xml b/apps/addressbook/build.xml index eed69a3fa..bf4e52713 100644 --- a/apps/addressbook/build.xml +++ b/apps/addressbook/build.xml @@ -1,5 +1,5 @@ - + @@ -8,6 +8,8 @@ + + @@ -36,24 +38,10 @@ - - - - - - - - - - - + + srcdir="${src}" destdir="${build}"> @@ -61,20 +49,6 @@ - - - - - - - - - - - @@ -91,29 +65,14 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + diff --git a/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java b/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java index 2fb20d2d7..36c9f6c0a 100644 --- a/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java +++ b/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java @@ -43,13 +43,13 @@ import net.i2p.util.SystemVersion; /** * Main class of addressbook. Performs updates, and runs the main loop. + * As of 0.9.30, package private, run with DaemonThread. * * @author Ragnarok * */ -public class Daemon { +class Daemon { public static final String VERSION = "2.0.4"; - private static final Daemon _instance = new Daemon(); private volatile boolean _running; private static final boolean DEBUG = false; private static final String DEFAULT_SUB = "http://i2p-projekt.i2p/hosts.txt"; @@ -787,14 +787,15 @@ public class Daemon { * others are ignored. */ public static void main(String[] args) { + Daemon daemon = new Daemon(); if (args != null && args.length > 0 && args[0].equals("test")) - _instance.test(args); + daemon.test(args); else - _instance.run(args); + daemon.run(args); } /** @since 0.9.26 */ - private static void test(String[] args) { + public static void test(String[] args) { Properties ctxProps = new Properties(); String PROP_FORCE = "i2p.naming.blockfile.writeInAppContext"; ctxProps.setProperty(PROP_FORCE, "true"); @@ -875,14 +876,14 @@ public class Daemon { * Call this to get the addressbook to reread its config and * refetch its subscriptions. */ - public static void wakeup() { - synchronized (_instance) { - _instance.notifyAll(); + public void wakeup() { + synchronized (this) { + notifyAll(); } } - public static void stop() { - _instance._running = false; + public void stop() { + _running = false; wakeup(); } } diff --git a/apps/addressbook/java/src/net/i2p/addressbook/DaemonThread.java b/apps/addressbook/java/src/net/i2p/addressbook/DaemonThread.java index 45a7893dd..a3ef36ce3 100644 --- a/apps/addressbook/java/src/net/i2p/addressbook/DaemonThread.java +++ b/apps/addressbook/java/src/net/i2p/addressbook/DaemonThread.java @@ -36,6 +36,7 @@ import net.i2p.util.I2PAppThread; public class DaemonThread extends I2PAppThread implements NamingServiceUpdater { private final String[] args; + private final Daemon daemon; /** * Construct a DaemonThread with the command line arguments args. @@ -44,6 +45,7 @@ public class DaemonThread extends I2PAppThread implements NamingServiceUpdater { */ public DaemonThread(String[] args) { this.args = args; + daemon = new Daemon(); } /* (non-Javadoc) @@ -56,18 +58,28 @@ public class DaemonThread extends I2PAppThread implements NamingServiceUpdater { //} catch (InterruptedException exp) { //} I2PAppContext.getGlobalContext().namingService().registerUpdater(this); - Daemon.main(this.args); - I2PAppContext.getGlobalContext().namingService().unregisterUpdater(this); + try { + if (args != null && args.length > 0 && args[0].equals("test")) + daemon.test(args); + else + daemon.run(args); + } finally { + I2PAppContext.getGlobalContext().namingService().unregisterUpdater(this); + } } public void halt() { - Daemon.stop(); + daemon.stop(); interrupt(); } /** - * The NamingServiceUpdater interface - * @param options ignored + * The NamingServiceUpdater interface. + * While this may be called directly, the recommended way + * is to call I2PAppContext.namingService().requestUpdate(Properties) + * which will call this. + * + * @param options ignored, may be null * @since 0.8.7 */ public void update(Properties options) { diff --git a/apps/addressbook/web.xml b/apps/addressbook/web.xml deleted file mode 100644 index d86d32289..000000000 --- a/apps/addressbook/web.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - XSSFilter - net.i2p.servlet.filters.XSSFilter - - - XSSFilter - /* - - - - addressbook - net.i2p.addressbook.Servlet - - home - ./addressbook - - 1 - - - - addressbook - /* - - - - - 30 - - true - - - diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java index 58f27742b..9d0b3de32 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java @@ -70,6 +70,7 @@ public class I2PSnarkUtil { private boolean _areFilesPublic; private List _openTrackers; private DHT _dht; + private long _startedTime; private static final int EEPGET_CONNECT_TIMEOUT = 45*1000; private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000; @@ -260,6 +261,8 @@ public class I2PSnarkUtil { if (opts.getProperty(I2PClient.PROP_SIGTYPE) == null) opts.setProperty(I2PClient.PROP_SIGTYPE, "EdDSA_SHA512_Ed25519"); _manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts); + if (_manager != null) + _startedTime = _context.clock().now(); _connecting = false; } if (_shouldUseDHT && _manager != null && _dht == null) @@ -295,6 +298,7 @@ public class I2PSnarkUtil { _dht.stop(); _dht = null; } + _startedTime = 0; I2PSocketManager mgr = _manager; // FIXME this can cause race NPEs elsewhere _manager = null; @@ -310,6 +314,16 @@ public class I2PSnarkUtil { _tmpDir.mkdirs(); } + /** + * When did we connect to the network? + * For RPC + * @return 0 if not connected + * @since 0.9.30 + */ + public long getStartedTime() { + return _startedTime; + } + /** connect to the given destination */ I2PSocket connect(PeerID peer) throws IOException { I2PSocketManager mgr = _manager; diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java index d2658ee5d..79f26b7f8 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java @@ -89,6 +89,7 @@ public class Peer implements Comparable */ //private static final long OPTION_AZMP = 0x1000000000000000l; private long options; + private final boolean _isIncoming; /** * Outgoing connection. @@ -103,6 +104,7 @@ public class Peer implements Comparable this.infohash = infohash; this.metainfo = metainfo; _id = __id.incrementAndGet(); + _isIncoming = false; //_log.debug("Creating a new peer with " + peerID.toString(), new Exception("creating")); } @@ -130,6 +132,16 @@ public class Peer implements Comparable _id = __id.incrementAndGet(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Creating a new peer " + peerID.toString(), new Exception("creating " + _id)); + _isIncoming = true; + } + + /** + * Is this an incoming connection? + * For RPC + * @since 0.9.30 + */ + public boolean isIncoming() { + return _isIncoming; } /** diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index 74f64a34c..350bab829 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; +import java.util.concurrent.atomic.AtomicInteger; import net.i2p.I2PAppContext; import net.i2p.client.streaming.I2PServerSocket; @@ -237,8 +238,10 @@ public class Snark private volatile boolean _autoStoppable; // String indicating main activity private volatile String activity = "Not started"; - private final long savedUploaded; - + private long savedUploaded; + private long _startedTime; + private static final AtomicInteger __RPCID = new AtomicInteger(); + private final int _rpcID = __RPCID.incrementAndGet(); /** * from main() via parseArguments() single torrent @@ -542,6 +545,7 @@ public class Snark starting = true; try { x_startTorrent(); + _startedTime = _util.getContext().clock().now(); } finally { starting = false; } @@ -633,16 +637,17 @@ public class Snark if (st != null) { // TODO: Cache the config-in-mem to compare vs config-on-disk // (needed for auto-save to not double-save in some cases) - //boolean changed = storage.isChanged() || getUploaded() != savedUploaded; - boolean changed = true; - if (changed && completeListener != null) - completeListener.updateStatus(this); + long nowUploaded = getUploaded(); + boolean changed = storage.isChanged() || nowUploaded != savedUploaded; try { storage.close(); } catch (IOException ioe) { System.out.println("Error closing " + torrent); ioe.printStackTrace(); } + savedUploaded = nowUploaded; + if (changed && completeListener != null) + completeListener.updateStatus(this); } if (fast) // HACK: See above if(!fast) @@ -1285,8 +1290,12 @@ public class Snark allChecked = true; checking = false; - if (storage.isChanged() && completeListener != null) + if (storage.isChanged() && completeListener != null) { completeListener.updateStatus(this); + // this saved the status, so reset the variables + storage.clearChanged(); + savedUploaded = getUploaded(); + } } public void storageCompleted(Storage storage) @@ -1295,8 +1304,12 @@ public class Snark _log.info("Completely received " + torrent); //storage.close(); //System.out.println("Completely received: " + torrent); - if (completeListener != null) + if (completeListener != null) { completeListener.torrentComplete(this); + // this saved the status, so reset the variables + savedUploaded = getUploaded(); + storage.clearChanged(); + } } public void setWantedPieces(Storage storage) @@ -1364,4 +1377,23 @@ public class Snark long limit = 1024l * _util.getMaxUpBW(); return total > limit; } + + /** + * A unique ID for this torrent, useful for RPC + * @return positive value unless you wrap around + * @since 0.9.30 + */ + public int getRPCID() { + return _rpcID; + } + + /** + * When did we start this torrent + * For RPC + * @return 0 if not started before. Not cleared when stopped. + * @since 0.9.30 + */ + public long getStartedTime() { + return _startedTime; + } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 39342e00f..448186ff6 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -27,7 +27,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; +import net.i2p.app.ClientApp; import net.i2p.app.ClientAppManager; +import net.i2p.app.ClientAppState; import net.i2p.crypto.SHA1Hash; import net.i2p.crypto.SigType; import net.i2p.data.Base64; @@ -51,7 +53,7 @@ import org.klomp.snark.dht.KRPC; /** * Manage multiple snarks */ -public class SnarkManager implements CompleteListener { +public class SnarkManager implements CompleteListener, ClientApp { /** * Map of (canonical) filename of the .torrent file to Snark instance. @@ -246,6 +248,13 @@ public class SnarkManager implements CompleteListener { */ public void start() { _running = true; + if ("i2psnark".equals(_contextName)) { + // Register with the ClientAppManager so the rpc plugin can find us + // only if default instance + ClientAppManager cmgr = _context.clientAppManager(); + if (cmgr != null) + cmgr.register(this); + } _peerCoordinatorSet = new PeerCoordinatorSet(); _connectionAcceptor = new ConnectionAcceptor(_util, _peerCoordinatorSet); _monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true); @@ -315,6 +324,12 @@ public class SnarkManager implements CompleteListener { _connectionAcceptor.halt(); _idleChecker.cancel(); stopAllTorrents(true); + if ("i2psnark".equals(_contextName)) { + // only if default instance + ClientAppManager cmgr = _context.clientAppManager(); + if (cmgr != null) + cmgr.unregister(this); + } if (_log.shouldWarn()) _log.warn("Snark stop() end"); } @@ -322,6 +337,46 @@ public class SnarkManager implements CompleteListener { /** @since 0.9.1 */ public boolean isStopping() { return _stopping; } + /** + * ClientApp method. Does nothing. + * Doesn't matter, we are only registering. + * @since 0.9.30 + */ + public void startup() {} + + /** + * ClientApp method. Does nothing. + * Doesn't matter, we are only registering. + * @since 0.9.30 + */ + public void shutdown(String[] args) {} + + /** + * ClientApp method. + * Doesn't matter, we are only registering. + * @return INITIALIZED always. + * @since 0.9.30 + */ + public ClientAppState getState() { + return ClientAppState.INITIALIZED; + } + + /** + * ClientApp method. + * @since 0.9.30 + */ + public String getName() { + return "i2psnark"; + } + + /** + * ClientApp method. + * @since 0.9.30 + */ + public String getDisplayName() { + return "i2psnark: " + _contextPath; + } + /** hook to I2PSnarkUtil for the servlet */ public I2PSnarkUtil util() { return _util; } @@ -440,6 +495,14 @@ public class SnarkManager implements CompleteListener { return f; } + /** + * For RPC + * @since 0.9.30 + */ + public File getConfigDir() { + return _configDir; + } + /** * Migrate the old flat config file to the new config dir * containing the config file minus the per-torrent entries, @@ -1526,9 +1589,9 @@ public class SnarkManager implements CompleteListener { * Called from servlet. This is only for the 'create torrent' form. * * @param metainfo the metainfo for the torrent - * @param bitfield the current completion status of the torrent + * @param bitfield the current completion status of the torrent, or null * @param filename the absolute path to save the metainfo to, generally ending in ".torrent", which is also the name of the torrent - * Must be a filesystem-safe name. + * Must be a filesystem-safe name. If null, will generate a name from the metainfo. * @param baseFile may be null, if so look in rootDataDir * @throws RuntimeException via Snark.fatal() * @return success @@ -1542,10 +1605,18 @@ public class SnarkManager implements CompleteListener { if (snark != null) { addMessage(_t("Torrent with this info hash is already running: {0}", snark.getBaseName())); return false; - } else { + } else if (bitfield != null) { saveTorrentStatus(metainfo, bitfield, null, baseFile, true, 0, true); // no file priorities } // so addTorrent won't recheck + if (filename == null) { + File f = new File(getDataDir(), Storage.filterName(metainfo.getName()) + ".torrent"); + if (f.exists()) { + addMessage(_t("Failed to copy torrent file to {0}", f.getAbsolutePath())); + _log.error("Torrent file already exists: " + f); + } + filename = f.getAbsolutePath(); + } try { locked_writeMetaInfo(metainfo, filename, areFilesPublic()); // hold the lock for a long time diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java index 264602653..11825d09f 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java @@ -305,6 +305,14 @@ public class Storage implements Closeable return changed; } + /** + * Clear the storage changed variable + * @since 0.9.30 + */ + void clearChanged() { + changed = false; + } + /** * File checking in progress. * @since 0.9.3 diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/NodeInfo.java b/apps/i2psnark/java/src/org/klomp/snark/dht/NodeInfo.java index 35d5761aa..f5bd718c8 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/dht/NodeInfo.java +++ b/apps/i2psnark/java/src/org/klomp/snark/dht/NodeInfo.java @@ -106,11 +106,11 @@ class NodeInfo extends SimpleDataStructure { if (parts.length != 4) throw new DataFormatException("Bad format"); byte[] nid = Base64.decode(parts[0]); - if (nid == null) + if (nid == null || nid.length != NID.HASH_LENGTH) throw new DataFormatException("Bad NID"); nID = new NID(nid); byte[] h = Base64.decode(parts[1]); - if (h == null) + if (h == null || h.length != Hash.HASH_LENGTH) throw new DataFormatException("Bad hash"); //hash = new Hash(h); hash = Hash.create(h); diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index 96cffc4e6..3bb59807e 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -1803,6 +1803,8 @@ public class I2PSnarkServlet extends BasicServlet { client = "Vuze" + getAzVersion(pid.getID()); else if ("CwsL".equals(ch)) client = "I2PSnarkXL"; + else if ("LVhE".equals(ch)) + client = "XD" + getAzVersion(pid.getID()); else if ("ZV".equals(ch.substring(2,4)) || "VUZP".equals(ch)) client = "Robert" + getRobtVersion(pid.getID()); else if (ch.startsWith("LV")) // LVCS 1.0.2?; LVRS 1.0.4 diff --git a/apps/i2psnark/jetty-i2psnark.xml b/apps/i2psnark/jetty-i2psnark.xml index 53d0e15e9..4a525ce8f 100644 --- a/apps/i2psnark/jetty-i2psnark.xml +++ b/apps/i2psnark/jetty-i2psnark.xml @@ -22,15 +22,24 @@ - + + + 1 + 0 + + + + + + + + + + + 127.0.0.1 8002 - 600000 - 1 - false - 5000 - 5000 - false + 600000 @@ -96,7 +105,7 @@ - + ./contexts 0 diff --git a/apps/i2psnark/readme-standalone.txt b/apps/i2psnark/readme-standalone.txt index fa35c65ee..7eb75c519 100644 --- a/apps/i2psnark/readme-standalone.txt +++ b/apps/i2psnark/readme-standalone.txt @@ -4,9 +4,24 @@ java -jar i2psnark.jar I2PSnark web ui will be at http://127.0.0.1:8002/i2psnark/ -Please note that http://127.0.0.1:8002/ will 404, to be fixed - I2PSnark is GPL'ed software, based on Snark (http://www.klomp.org/) to run on top of I2P (https://geti2p.net/) within a webserver (such as the bundled Jetty from https://www.eclipse.org/jetty/). For more information about I2PSnark, get in touch with the folks at http://forum.i2p2.de/ + + +To add RPC support: + +1) Stop i2psnark standalone if running. + +2a) If you have the i2psnark-rpc plugin installed in your router already, + copy the file ~/.i2p/plugins/i2psnark-rpc/console/webapps/transmission.war + to the webapps/ directory in your standalone install. + +2b) If you do not have the i2psnark-rpc plugin installed, get the i2p.plugins.i2psnark-rpc + branch out of monotone, build with 'ant war', and copy the file src/build/transmission.war.jar + to the file webapps/transmission.war in your standalone install. + +3) Start i2psnark standalone as usual. The transmission web interface will be at + http://127.0.0.1:8002/transmission/web/ or if you have transmission-remote installed, + test with 'transmission-remote 8002 -l' diff --git a/apps/i2ptunnel/java/build.xml b/apps/i2ptunnel/java/build.xml index 93f5cc339..c9a3a74c1 100644 --- a/apps/i2ptunnel/java/build.xml +++ b/apps/i2ptunnel/java/build.xml @@ -277,14 +277,17 @@ - + + - - + + + + @@ -309,12 +312,14 @@ - + + - - + + + diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java index a3c9f8566..fa9d26736 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java @@ -313,8 +313,9 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna } /** - * Add a subsession to a shared client if necessary. + * Add a DSA_SHA1 subsession to the shared client if necessary. * + * @return subsession, or null if none was added * @since 0.9.20 */ protected static synchronized I2PSession addSubsession(I2PTunnel tunnel) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index 610c1fe24..cae60376d 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -204,7 +204,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn "Proxy-Connection: close\r\n"+ "\r\n" + "

I2P ERROR: SSL to I2P address rejected

" + - "SSL for to .i2p addresses denied by configuration." + + "SSL to .i2p addresses denied by configuration." + "You may change the configuration in I2PTunnel"; /** diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java index 87dd7e937..5da192609 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java @@ -210,13 +210,13 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { _log.error("Error connecting to IRC server " + remoteHost + ':' + remotePort, ex); } catch (IOException ex) { try { - socket.close(); + socket.reset(); } catch (IOException ioe) {} if (_log.shouldLog(Log.WARN)) _log.warn("Error while receiving the new IRC Connection", ex); } catch (OutOfMemoryError oom) { try { - socket.close(); + socket.reset(); } catch (IOException ioe) {} if (_log.shouldLog(Log.ERROR)) _log.error("OOM in IRC server", oom); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index 46a030dd4..bd5df915a 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -18,6 +18,7 @@ import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.security.GeneralSecurityException; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; @@ -29,12 +30,14 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.ThreadFactory; import net.i2p.I2PException; +import net.i2p.client.I2PClient; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.client.streaming.I2PServerSocket; import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketManager; import net.i2p.client.streaming.I2PSocketManagerFactory; +import net.i2p.crypto.SigType; import net.i2p.data.Base64; import net.i2p.data.Hash; import net.i2p.util.EventDispatcher; @@ -67,6 +70,8 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { private static final boolean DEFAULT_USE_POOL = true; public static final String PROP_USE_SSL = "useSSL"; public static final String PROP_UNIQUE_LOCAL = "enableUniqueLocal"; + /** @since 0.9.30 */ + public static final String PROP_ALT_PKF = "altPrivKeyFile"; /** apparently unused */ protected static volatile long __serverId = 0; /** max number of threads - this many slowlorisses will DOS this server, but too high could OOM the JVM */ @@ -217,6 +222,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { portNum, props); rv.setName("I2PTunnel Server"); getTunnel().addSession(rv.getSession()); + String alt = props.getProperty(PROP_ALT_PKF); + if (alt != null) + addSubsession(rv, alt); return rv; } catch (I2PSessionException ise) { throw new IllegalArgumentException("Can't create socket manager", ise); @@ -225,6 +233,44 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { } } + /** + * Add a non-DSA_SHA1 subsession to the DSA_SHA1 server if necessary. + * + * @return subsession, or null if none was added + * @since 0.9.30 + */ + private I2PSession addSubsession(I2PSocketManager sMgr, String alt) { + File altFile = TunnelController.filenameToFile(alt); + if (alt == null) + return null; + I2PSession sess = sMgr.getSession(); + if (sess.getMyDestination().getSigType() != SigType.DSA_SHA1) + return null; + Properties props = new Properties(); + props.putAll(getTunnel().getClientOptions()); + // fixme get actual sig type + String name = props.getProperty("inbound.nickname"); + if (name != null) + props.setProperty("inbound.nickname", name + " (EdDSA)"); + name = props.getProperty("outbound.nickname"); + if (name != null) + props.setProperty("outbound.nickname", name + " (EdDSA)"); + props.setProperty(I2PClient.PROP_SIGTYPE, "EdDSA_SHA512_Ed25519"); + FileInputStream privData = null; + try { + privData = new FileInputStream(altFile); + return sMgr.addSubsession(privData, props); + } catch (IOException ioe) { + _log.error("Failed to add subssession", ioe); + return null; + } catch (I2PSessionException ise) { + _log.error("Failed to add subssession", ise); + return null; + } finally { + if (privData != null) try { privData.close(); } catch (IOException ioe) {} + } + } + /** * Warning, blocks while connecting to router and building tunnels; @@ -238,6 +284,22 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { while (sockMgr.getSession().isClosed()) { try { sockMgr.getSession().connect(); + // Now connect the subsessions, if any + List subs = sockMgr.getSubsessions(); + if (!subs.isEmpty()) { + for (I2PSession sub : subs) { + try { + sub.connect(); + if (_log.shouldInfo()) + _log.info("Connected subsession " + sub); + } catch (I2PSessionException ise) { + // not fatal? + String msg = "Unable to connect subsession " + sub; + this.l.log(msg); + _log.error(msg, ise); + } + } + } } catch (I2PSessionException ise) { // try to make this error sensible as it will happen... String portNum = getTunnel().port; @@ -618,7 +680,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { " [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]"); } catch (SocketException ex) { try { - socket.close(); + socket.reset(); } catch (IOException ioe) {} if (_log.shouldLog(Log.ERROR)) _log.error("Error connecting to server " + remoteHost + ':' + remotePort, ex); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 9079efb01..1c1ed8c58 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -3,6 +3,7 @@ package net.i2p.i2ptunnel; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -16,12 +17,22 @@ import net.i2p.I2PException; import net.i2p.client.I2PClient; import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PSession; +import net.i2p.client.I2PSessionException; +import net.i2p.crypto.KeyGenerator; import net.i2p.crypto.SigType; import net.i2p.data.Destination; +import net.i2p.data.KeyCertificate; +import net.i2p.data.PrivateKey; +import net.i2p.data.PrivateKeyFile; +import net.i2p.data.PublicKey; +import net.i2p.data.SigningPrivateKey; +import net.i2p.data.SigningPublicKey; +import net.i2p.data.SimpleDataStructure; import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel; import net.i2p.util.FileUtil; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; +import net.i2p.util.RandomSource; import net.i2p.util.SecureFile; import net.i2p.util.SecureFileOutputStream; import net.i2p.util.SystemVersion; @@ -83,6 +94,8 @@ public class TunnelController implements Logging { private static final String OPT_TAGS_SEND = PFX_OPTION + "crypto.tagsToSend"; private static final String OPT_LOW_TAGS = PFX_OPTION + "crypto.lowTagThreshold"; private static final String OPT_SIG_TYPE = PFX_OPTION + I2PClient.PROP_SIGTYPE; + /** @since 0.9.30 */ + private static final String OPT_ALT_PKF = PFX_OPTION + I2PTunnelServer.PROP_ALT_PKF; /** all of these @since 0.9.14 */ public static final String TYPE_CONNECT = "connectclient"; @@ -106,7 +119,7 @@ public class TunnelController implements Logging { */ public static final SigType PREFERRED_SIGTYPE; static { - if (SystemVersion.isARM() || SystemVersion.isGNU() || SystemVersion.isAndroid()) { + if (SystemVersion.isGNU() || SystemVersion.isAndroid()) { if (SigType.ECDSA_SHA256_P256.isAvailable()) PREFERRED_SIGTYPE = SigType.ECDSA_SHA256_P256; else @@ -146,8 +159,13 @@ public class TunnelController implements Logging { setConfig(config, prefix); _messages = new ArrayList(4); boolean keyOK = true; - if (createKey && (getType().endsWith("server") || getPersistentClientKey())) + if (createKey && (!isClient() || getPersistentClientKey())) { keyOK = createPrivateKey(); + if (keyOK && !isClient() && !getType().equals(TYPE_STREAMR_SERVER)) { + // check rv? + createAltPrivateKey(); + } + } _state = keyOK && getStartOnLoad() ? TunnelState.START_ON_LOAD : TunnelState.STOPPED; } @@ -186,7 +204,7 @@ public class TunnelController implements Logging { String destStr = dest.toBase64(); log("Private key created and saved in " + keyFile.getAbsolutePath()); log("You should backup this file in a secure place."); - log("New destination: " + destStr); + log("New alternate destination: " + destStr); String b32 = dest.toBase32(); log("Base32: " + b32); File backupDir = new SecureFile(I2PAppContext.getGlobalContext().getConfigDir(), KEY_BACKUP_DIR); @@ -214,6 +232,101 @@ public class TunnelController implements Logging { return true; } + /** + * Creates alternate Destination with the same encryption keys as the primary Destination, + * but a different signing key. + * + * Must have already called createPrivateKey() successfully. + * Does nothing unless option OPT_ALT_PKF is set with the privkey file name. + * Does nothing if the file already exists. + * + * @return success + * @since 0.9.30 + */ + private boolean createAltPrivateKey() { + if (PREFERRED_SIGTYPE == SigType.DSA_SHA1) + return false; + File keyFile = getPrivateKeyFile(); + if (keyFile == null) + return false; + if (!keyFile.exists()) + return false; + File altFile = getAlternatePrivateKeyFile(); + if (altFile == null) + return false; + if (altFile.equals(keyFile)) + return false; + if (altFile.exists()) + return true; + PrivateKeyFile pkf = new PrivateKeyFile(keyFile); + FileOutputStream out = null; + try { + Destination dest = pkf.getDestination(); + if (dest == null) + return false; + if (dest.getSigType() != SigType.DSA_SHA1) + return false; + PublicKey pub = dest.getPublicKey(); + PrivateKey priv = pkf.getPrivKey(); + SimpleDataStructure[] signingKeys = KeyGenerator.getInstance().generateSigningKeys(PREFERRED_SIGTYPE); + SigningPublicKey signingPubKey = (SigningPublicKey) signingKeys[0]; + SigningPrivateKey signingPrivKey = (SigningPrivateKey) signingKeys[1]; + KeyCertificate cert = new KeyCertificate(signingPubKey); + Destination d = new Destination(); + d.setPublicKey(pub); + d.setSigningPublicKey(signingPubKey); + d.setCertificate(cert); + int len = signingPubKey.length(); + if (len < 128) { + byte[] pad = new byte[128 - len]; + RandomSource.getInstance().nextBytes(pad); + d.setPadding(pad); + } else if (len > 128) { + // copy of excess data handled in KeyCertificate constructor + } + + out = new SecureFileOutputStream(altFile); + d.writeBytes(out); + priv.writeBytes(out); + signingPrivKey.writeBytes(out); + try { out.close(); } catch (IOException ioe) {} + + String destStr = d.toBase64(); + log("Alternate private key created and saved in " + altFile.getAbsolutePath()); + log("You should backup this file in a secure place."); + log("New destination: " + destStr); + String b32 = d.toBase32(); + log("Base32: " + b32); + File backupDir = new SecureFile(I2PAppContext.getGlobalContext().getConfigDir(), KEY_BACKUP_DIR); + if (backupDir.isDirectory() || backupDir.mkdir()) { + String name = b32 + '-' + I2PAppContext.getGlobalContext().clock().now() + ".dat"; + File backup = new File(backupDir, name); + if (FileUtil.copy(altFile, backup, false, true)) { + SecureFileOutputStream.setPerms(backup); + log("Alternate private key backup saved to " + backup.getAbsolutePath()); + } + } + return true; + } catch (GeneralSecurityException e) { + log("Error creating keys " + e); + return false; + } catch (I2PSessionException e) { + log("Error creating keys " + e); + return false; + } catch (I2PException e) { + log("Error creating keys " + e); + return false; + } catch (IOException e) { + log("Error creating keys " + e); + return false; + } catch (RuntimeException e) { + log("Error creating keys " + e); + return false; + } finally { + if (out != null) try { out.close(); } catch (IOException ioe) {} + } + } + public void startTunnelBackground() { synchronized (this) { if (_state != TunnelState.STOPPED && _state != TunnelState.START_ON_LOAD) @@ -267,13 +380,17 @@ public class TunnelController implements Logging { } // Config options may have changed since instantiation, so do this again. // Or should we take it out of the constructor completely? - if (type.endsWith("server") || getPersistentClientKey()) { + if (!isClient() || getPersistentClientKey()) { boolean ok = createPrivateKey(); if (!ok) { changeState(TunnelState.STOPPED); log("Failed to start tunnel " + getName() + " as the private key file could not be created"); return; } + if (!isClient() && !getType().equals(TYPE_STREAMR_SERVER)) { + // check rv? + createAltPrivateKey(); + } } setI2CPOptions(); setSessionOptions(); @@ -641,6 +758,7 @@ public class TunnelController implements Logging { props.setProperty(key, val); } } + Properties oldConfig = _config; _config = props; // Set up some per-type defaults @@ -685,6 +803,15 @@ public class TunnelController implements Logging { return; } } + + if (oldConfig != null) { + if (configChanged(_config, oldConfig, PROP_FILE) || + configChanged(_config, oldConfig, OPT_ALT_PKF) || + configChanged(_config, oldConfig, OPT_SIG_TYPE)) { + log("Tunnel must be stopped and restarted for private key file changes to take effect"); + } + } + // Running, so check sessions Collection sessions = getAllSessions(); if (sessions.isEmpty()) { @@ -704,6 +831,17 @@ public class TunnelController implements Logging { } } + /** + * Is property p different in p1 and p2? + * @since 0.9.30 + */ + private static boolean configChanged(Properties p1, Properties p2, String p) { + String s1 = p1.getProperty(p); + String s2 = p2.getProperty(p); + return (s1 != null && !s1.equals(s2)) || + (s1 == null && s2 != null); + } + /** * @return a copy */ @@ -797,7 +935,25 @@ public class TunnelController implements Logging { * @since 0.9.17 */ public File getPrivateKeyFile() { - String f = getPrivKeyFile(); + return filenameToFile(getPrivKeyFile()); + } + + /** + * Does not necessarily exist. + * @return absolute path or null if unset + * @since 0.9.30 + */ + public File getAlternatePrivateKeyFile() { + return filenameToFile(_config.getProperty(OPT_ALT_PKF)); + } + + /** + * Does not necessarily exist. + * @param f relative or absolute path, may be null + * @return absolute path or null + * @since 0.9.30 + */ + static File filenameToFile(String f) { if (f == null) return null; f = f.trim(); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCServer.java index 8010a110f..8b49b6995 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCServer.java @@ -119,7 +119,7 @@ public class I2PTunnelDCCServer extends I2PTunnelServer { _active.put(Integer.valueOf(myPort), local); } catch (SocketException ex) { try { - socket.close(); + socket.reset(); } catch (IOException ioe) {} _log.error("Error relaying incoming DCC connection to IRC client at " + local.ia + ':' + local.port, ex); } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SocketWrapper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SocketWrapper.java index e6b3b15f6..6e4ecc565 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SocketWrapper.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SocketWrapper.java @@ -87,6 +87,14 @@ class SocketWrapper implements I2PSocket { socket.close(); } + /** + * Just calls close() + * @since 0.9.30 + */ + public void reset() throws IOException { + close(); + } + public boolean isClosed() { return socket.isClosed(); } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java index 26f4265e0..2a684c7e4 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java @@ -264,14 +264,23 @@ public class GeneralHelper { return (tun != null && tun.getSpoofedHost() != null) ? tun.getSpoofedHost() : ""; } + /** + * @return path, non-null, non-empty + */ public String getPrivateKeyFile(int tunnel) { return getPrivateKeyFile(_group, tunnel); } + /** + * @return path, non-null, non-empty + */ public String getPrivateKeyFile(TunnelControllerGroup tcg, int tunnel) { TunnelController tun = getController(tcg, tunnel); - if (tun != null && tun.getPrivKeyFile() != null) - return tun.getPrivKeyFile(); + if (tun != null) { + String rv = tun.getPrivKeyFile(); + if (rv != null) + return rv; + } if (tunnel < 0) tunnel = tcg == null ? 999 : tcg.getControllers().size(); String rv = "i2ptunnel" + tunnel + "-privKeys.dat"; @@ -284,6 +293,28 @@ public class GeneralHelper { return rv; } + /** + * @return path or "" + * @since 0.9.30 + */ + public String getAltPrivateKeyFile(int tunnel) { + return getAltPrivateKeyFile(_group, tunnel); + } + + /** + * @return path or "" + * @since 0.9.30 + */ + public String getAltPrivateKeyFile(TunnelControllerGroup tcg, int tunnel) { + TunnelController tun = getController(tcg, tunnel); + if (tun != null) { + File f = tun.getAlternatePrivateKeyFile(); + if (f != null) + return f.getAbsolutePath(); + } + return ""; + } + public String getClientInterface(int tunnel) { TunnelController tun = getController(tunnel); if (tun != null) { @@ -357,6 +388,29 @@ public class GeneralHelper { return null; } + /** + * Works even if tunnel is not running. + * @return Destination or null + * @since 0.9.30 + */ + public Destination getAltDestination(int tunnel) { + TunnelController tun = getController(tunnel); + if (tun != null) { + // do this the hard way + File keyFile = tun.getAlternatePrivateKeyFile(); + if (keyFile != null) { + PrivateKeyFile pkf = new PrivateKeyFile(keyFile); + try { + Destination rv = pkf.getDestination(); + if (rv != null) + return rv; + } catch (I2PException e) { + } catch (IOException e) {} + } + } + return null; + } + public boolean shouldStartAutomatically(int tunnel) { TunnelController tun = getController(tunnel); return tun != null ? tun.getStartOnLoad() : false; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java index 6dd91cce3..d381939a1 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java @@ -161,6 +161,16 @@ public class TunnelConfig { public String getPrivKeyFile() { return _privKeyFile; } + + /** + * What filename is this server tunnel's alternate private keys stored in + * @since 0.9.30 + */ + public void setAltPrivKeyFile(String file) { + if (file != null) + _otherOptions.put(I2PTunnelServer.PROP_ALT_PKF, file.trim()); + } + /** * If called with any value, we want this tunnel to start whenever it is * loaded (aka right now and whenever the router is started up) @@ -725,7 +735,8 @@ public class TunnelConfig { PROP_MAX_CONNS_MIN, PROP_MAX_CONNS_HOUR, PROP_MAX_CONNS_DAY, PROP_MAX_TOTAL_CONNS_MIN, PROP_MAX_TOTAL_CONNS_HOUR, PROP_MAX_TOTAL_CONNS_DAY, PROP_MAX_STREAMS, I2PClient.PROP_SIGTYPE, - "inbound.randomKey", "outbound.randomKey", "i2cp.leaseSetSigningPrivateKey", "i2cp.leaseSetPrivateKey" + "inbound.randomKey", "outbound.randomKey", "i2cp.leaseSetSigningPrivateKey", "i2cp.leaseSetPrivateKey", + I2PTunnelServer.PROP_ALT_PKF }; private static final String _httpServerOpts[] = { I2PTunnelHTTPServer.OPT_POST_WINDOW, diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index f252faade..99317e54b 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -70,6 +70,14 @@ public class EditBean extends IndexBean { public String getPrivateKeyFile(int tunnel) { return _helper.getPrivateKeyFile(tunnel); } + + /** + * @return path or "" + * @since 0.9.30 + */ + public String getAltPrivateKeyFile(int tunnel) { + return _helper.getAltPrivateKeyFile(tunnel); + } /**** public String getNameSignature(int tunnel) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index fe1c30b17..be841b817 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -488,6 +488,39 @@ public class IndexBean { return d.toBase32(); return ""; } + + /** + * Works even if tunnel is not running. + * @return Destination or null + * @since 0.9.30 + */ + protected Destination getAltDestination(int tunnel) { + return _helper.getAltDestination(tunnel); + } + + /** + * Works even if tunnel is not running. + * @return Base64 or "" + * @since 0.9.30 + */ + public String getAltDestinationBase64(int tunnel) { + Destination d = getAltDestination(tunnel); + if (d != null) + return d.toBase64(); + return ""; + } + + /** + * Works even if tunnel is not running. + * @return "{52 chars}.b32.i2p" or "" + * @since 0.9.30 + */ + public String getAltDestHashBase32(int tunnel) { + Destination d = getAltDestination(tunnel); + if (d != null) + return d.toBase32(); + return ""; + } /** * For index.jsp @@ -613,10 +646,20 @@ public class IndexBean { public void setSpoofedHost(String host) { _config.setSpoofedHost(host); } + /** What filename is this server tunnel's private keys stored in */ public void setPrivKeyFile(String file) { _config.setPrivKeyFile(file); } + + /** + * What filename is this server tunnel's alternate private keys stored in + * @since 0.9.30 + */ + public void setAltPrivKeyFile(String file) { + _config.setAltPrivKeyFile(file); + } + /** * If called with any value (and the form submitted with action=Remove), * we really do want to stop and remove the tunnel. diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index 0d41b2852..9d5704258 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -735,8 +735,9 @@ input.default { width: 1px; height: 1px; visibility: hidden; } <% **********************/ %> - <% if (true /* editBean.isAdvanced() */ ) { - int currentSigType = editBean.getSigType(curTunnel, tunnelType); + <% + int currentSigType = editBean.getSigType(curTunnel, tunnelType); + if (true /* editBean.isAdvanced() */ ) { %> @@ -779,6 +780,50 @@ input.default { width: 1px; height: 1px; visibility: hidden; } <% } // isAdvanced %> + <% + /* alternate dest, only if current dest is set and is DSA_SHA1 */ + + if (currentSigType == 0 && !"".equals(b64) && !"streamrserver".equals(tunnelType)) { + %>
+ + +
+ + <% + String ab64 = editBean.getAltDestinationBase64(curTunnel); + if (!"".equals(ab64)) { + %>
+ + +
+
+ + <%=editBean.getAltDestHashBase32(curTunnel)%> +
+
+ <% + ab64 = ab64.replace("=", "%3d"); + String name = editBean.getSpoofedHost(curTunnel); + if (name == null || name.equals("")) + name = editBean.getTunnelName(curTunnel); + // mysite.i2p is set in the installed i2ptunnel.config + if (name != null && !name.equals("") && !name.equals("mysite.i2p") && !name.contains(" ") && name.endsWith(".i2p")) { + %> + <%=intl._t("Add to local addressbook")%> + <% + } else { + %> + <%=intl._t("Set name with .i2p suffix to enable QR code generation")%> + <% + } // name + %>
+ <% + } // ab64 + %>

+ <% } // currentSigType %> + <%=intl._t("Custom options")%> diff --git a/apps/i2ptunnel/jsp/register.jsp b/apps/i2ptunnel/jsp/register.jsp index d72b4e1d5..0b3c2aff1 100644 --- a/apps/i2ptunnel/jsp/register.jsp +++ b/apps/i2ptunnel/jsp/register.jsp @@ -91,6 +91,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; } } %> + <% if (b64 == null || b64.length() < 516) { @@ -307,12 +309,40 @@ input.default { width: 1px; height: 1px; visibility: hidden; } <% } else { + // If set, use the configured alternate destination as the new alias destination, + // and the configured primary destination as the inner signer. + // This is backwards from all the other ones, so we have to make a second HostTxtEntry just for this. + SigningPrivateKey spk3 = null; + String altdest = null; + String altdestfile = editBean.getAltPrivateKeyFile(curTunnel); + if (altdestfile.length() > 0) { + try { + PrivateKeyFile pkf3 = new PrivateKeyFile(altdestfile); + altdest = pkf3.getDestination().toBase64(); + if (!b64.equals(altdest)) { + // disallow dup + spk3 = pkf3.getSigningPrivKey(); + } + } catch (Exception e) {} + } + if (spk3 != null) { + OrderedProperties props2 = new OrderedProperties(); + HostTxtEntry he2 = new HostTxtEntry(name, altdest, props2); + props2.setProperty(HostTxtEntry.PROP_ACTION, HostTxtEntry.ACTION_ADDDEST); + props2.setProperty(HostTxtEntry.PROP_OLDDEST, b64); + he2.signInner(spk); + he2.sign(spk3); + %> + <%=intl._t("This will add an alternate destination for {0}", name)%> +<% + } else { %><%=intl._t("This tunnel must be configured with the new destination.")%>  <%=intl._t("Enter old destination below.")%> <% - } + } // spk3 + } // spk2 %> - + <% diff --git a/apps/jetty/apache-tomcat-deployer/NOTICE b/apps/jetty/apache-tomcat-deployer/NOTICE deleted file mode 100644 index 130d56e7d..000000000 --- a/apps/jetty/apache-tomcat-deployer/NOTICE +++ /dev/null @@ -1,22 +0,0 @@ -Apache Tomcat -Copyright 1999-2016 The Apache Software Foundation - -This product includes software developed at -The Apache Software Foundation (http://www.apache.org/). - -The Windows Installer is built with the Nullsoft -Scriptable Install System (NSIS), which is -open source software. The original software and -related information is available at -http://nsis.sourceforge.net. - -Java compilation software for JSP pages is provided by the Eclipse -JDT Core Batch Compiler component, which is open source software. -The original software and related information is available at -http://www.eclipse.org/jdt/core/. - -The original XML Schemas for Java EE Deployment Descriptors: - - javaee_5.xsd - - javaee_web_services_1_2.xsd - - javaee_web_services_client_1_2.xsd -may be obtained from http://java.sun.com/xml/ns/javaee/ diff --git a/apps/jetty/apache-tomcat-deployer/README-i2p.txt b/apps/jetty/apache-tomcat-deployer/README-i2p.txt deleted file mode 100644 index 6b8c0edf3..000000000 --- a/apps/jetty/apache-tomcat-deployer/README-i2p.txt +++ /dev/null @@ -1,26 +0,0 @@ -This is Apache Tomcat 6.x, supporting Servlet 2.5 and JSP 2.1. -The Glassfish JSP 2.1 bundled in Jetty 6 is way too old. - -Retrieved from the file - apache-tomcat-6.0.48-deployer.tar.gz - -minus the following files and directores: - - build.xml - deployer-howto.html - images/* - lib/catalina* - lib/jsp-api.jar (see below) - lib/servlet-api.jar (see below) - LICENSE (see ../../../licenses/LICENSE-Apache2.0.txt, it's also inside every jar) - RELEASE-NOTES - - -We could use the following API jars from Apache Tomcat 7.x, supporting Servlet 3.0 and JSP 2.2, -that are required for Jetty 8, but we just bundle the ones from Jetty 8 instead: - - lib/jsp-api.jar - lib/servlet-api.jar - -For more info: -http://tomcat.apache.org/whichversion.html diff --git a/apps/jetty/apache-tomcat-deployer/lib/el-api.jar b/apps/jetty/apache-tomcat-deployer/lib/el-api.jar deleted file mode 100644 index 2598a4a8f..000000000 Binary files a/apps/jetty/apache-tomcat-deployer/lib/el-api.jar and /dev/null differ diff --git a/apps/jetty/apache-tomcat-deployer/lib/jasper-el.jar b/apps/jetty/apache-tomcat-deployer/lib/jasper-el.jar deleted file mode 100644 index b7205e347..000000000 Binary files a/apps/jetty/apache-tomcat-deployer/lib/jasper-el.jar and /dev/null differ diff --git a/apps/jetty/apache-tomcat-deployer/lib/jasper.jar b/apps/jetty/apache-tomcat-deployer/lib/jasper.jar deleted file mode 100644 index ca2d7269d..000000000 Binary files a/apps/jetty/apache-tomcat-deployer/lib/jasper.jar and /dev/null differ diff --git a/apps/jetty/apache-tomcat-deployer/lib/tomcat-juli.jar b/apps/jetty/apache-tomcat-deployer/lib/tomcat-juli.jar deleted file mode 100644 index c5e0f8f77..000000000 Binary files a/apps/jetty/apache-tomcat-deployer/lib/tomcat-juli.jar and /dev/null differ diff --git a/apps/jetty/apache-tomcat/README-i2p.txt b/apps/jetty/apache-tomcat/README-i2p.txt deleted file mode 100644 index 015ff4736..000000000 --- a/apps/jetty/apache-tomcat/README-i2p.txt +++ /dev/null @@ -1,10 +0,0 @@ -This is Apache Tomcat 6.x, supporting Servlet 2.5 and JSP 2.1. - -Retrieved from the file - apache-tomcat-6.0.48.tar.gz - -containing only a small subset of lib/tomcat-coyote.jar. - -See the buildTomcatUtilJar target in ../build.xml for the classes extracted and more information. - -LICENSE: see ../../../licenses/LICENSE-Apache2.0.txt diff --git a/apps/jetty/apache-tomcat/lib/tomcat-coyote-util.jar b/apps/jetty/apache-tomcat/lib/tomcat-coyote-util.jar deleted file mode 100644 index a45499414..000000000 Binary files a/apps/jetty/apache-tomcat/lib/tomcat-coyote-util.jar and /dev/null differ diff --git a/apps/jetty/build.xml b/apps/jetty/build.xml index 47b88cb5d..f3de9bffa 100644 --- a/apps/jetty/build.xml +++ b/apps/jetty/build.xml @@ -3,7 +3,7 @@ - + + + - + - + + - - - - @@ -41,7 +40,7 @@ - + @@ -53,7 +52,7 @@ - + @@ -120,10 +119,17 @@ - + + - + + - - + + + + + + + + - - - - - - - - - - + @@ -233,34 +225,47 @@ - - - - + + + + + + - - - + + + + + + + + - - + - + + + diff --git a/apps/jetty/java/src/net/i2p/jetty/I2PLogger.java b/apps/jetty/java/src/net/i2p/jetty/I2PLogger.java index 28b011561..158696397 100644 --- a/apps/jetty/java/src/net/i2p/jetty/I2PLogger.java +++ b/apps/jetty/java/src/net/i2p/jetty/I2PLogger.java @@ -263,4 +263,11 @@ public class I2PLogger implements Logger public String getName() { return "net.i2p.jetty.I2PLogger"; } + + /** + * @since Jetty 9 + */ + public void debug(String msg, long arg) { + debug(msg, Long.valueOf(arg), null); + } } diff --git a/apps/jetty/java/src/net/i2p/jetty/I2PRequestLog.java b/apps/jetty/java/src/net/i2p/jetty/I2PRequestLog.java index 222960780..83c82b591 100644 --- a/apps/jetty/java/src/net/i2p/jetty/I2PRequestLog.java +++ b/apps/jetty/java/src/net/i2p/jetty/I2PRequestLog.java @@ -24,7 +24,6 @@ import java.util.TimeZone; import javax.servlet.http.Cookie; -import org.eclipse.jetty.http.HttpHeaders; import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.RequestLog; @@ -81,7 +80,7 @@ public class I2PRequestLog extends AbstractLifeCycle implements RequestLog private transient OutputStream _out; private transient OutputStream _fileOut; private transient DateCache _logDateCache; - private transient PathMap _ignorePathMap; + private transient PathMap _ignorePathMap; private transient Writer _writer; private transient ArrayList _buffers; private transient char[] _copy; @@ -286,7 +285,7 @@ public class I2PRequestLog extends AbstractLifeCycle implements RequestLog String addr = null; if (_preferProxiedForAddress) { - addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR); + addr = request.getHeader("X-Forwarded-For"); } if (addr == null) { @@ -310,7 +309,9 @@ public class I2PRequestLog extends AbstractLifeCycle implements RequestLog if (_logDateCache!=null) buf.append(_logDateCache.format(request.getTimeStamp())); else - buf.append(request.getTimeStampBuffer().toString()); + //buf.append(request.getTimeStampBuffer().toString()); + // TODO SimpleDateFormat or something + buf.append(request.getTimeStamp()); buf.append("] \""); buf.append(request.getMethod()); @@ -358,7 +359,7 @@ public class I2PRequestLog extends AbstractLifeCycle implements RequestLog { synchronized(_writer) { - buf.append(StringUtil.__LINE_SEPARATOR); + buf.append(System.getProperty("line.separator", "\n")); int l=buf.length(); if (l>_copy.length) l=_copy.length; @@ -412,7 +413,7 @@ public class I2PRequestLog extends AbstractLifeCycle implements RequestLog _writer.write(Long.toString(System.currentTimeMillis() - request.getTimeStamp())); } - _writer.write(StringUtil.__LINE_SEPARATOR); + _writer.write(System.getProperty("line.separator", "\n")); _writer.flush(); } } @@ -429,7 +430,7 @@ public class I2PRequestLog extends AbstractLifeCycle implements RequestLog Response response, Writer writer) throws IOException { - String referer = request.getHeader(HttpHeaders.REFERER); + String referer = request.getHeader("Referer"); if (referer == null) writer.write("\"-\" "); else @@ -439,7 +440,7 @@ public class I2PRequestLog extends AbstractLifeCycle implements RequestLog writer.write("\" "); } - String agent = request.getHeader(HttpHeaders.USER_AGENT); + String agent = request.getHeader("User-Agent"); if (agent == null) writer.write("\"-\" "); else @@ -455,8 +456,7 @@ public class I2PRequestLog extends AbstractLifeCycle implements RequestLog { if (_logDateFormat!=null) { - _logDateCache = new DateCache(_logDateFormat, _logLocale); - _logDateCache.setTimeZoneID(_logTimeZone); + _logDateCache = new DateCache(_logDateFormat, _logLocale, _logTimeZone); } if (_filename != null) @@ -472,7 +472,7 @@ public class I2PRequestLog extends AbstractLifeCycle implements RequestLog if (_ignorePaths != null && _ignorePaths.length > 0) { - _ignorePathMap = new PathMap(); + _ignorePathMap = new PathMap(); for (int i = 0; i < _ignorePaths.length; i++) _ignorePathMap.put(_ignorePaths[i], _ignorePaths[i]); } diff --git a/apps/jetty/java/src/net/i2p/jetty/JettyStart.java b/apps/jetty/java/src/net/i2p/jetty/JettyStart.java index 2c1b6c255..f068a20c0 100644 --- a/apps/jetty/java/src/net/i2p/jetty/JettyStart.java +++ b/apps/jetty/java/src/net/i2p/jetty/JettyStart.java @@ -30,6 +30,7 @@ import net.i2p.util.I2PAppThread; import net.i2p.util.PortMapper; import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.resource.Resource; @@ -130,13 +131,17 @@ public class JettyStart implements ClientApp { Server server = (Server) lc; Connector[] connectors = server.getConnectors(); if (connectors.length > 0) { - int port = connectors[0].getPort(); - if (port > 0) { - _port = port; - String host = connectors[0].getHost(); - if (host.equals("0.0.0.0") || host.equals("::")) - host = "127.0.0.1"; - _context.portMapper().register(PortMapper.SVC_EEPSITE, host, port); + Connector conn = connectors[0]; + if (conn instanceof NetworkConnector) { + NetworkConnector nconn = (NetworkConnector) conn; + int port = nconn.getPort(); + if (port > 0) { + _port = port; + String host = nconn.getHost(); + if (host.equals("0.0.0.0") || host.equals("::")) + host = "127.0.0.1"; + _context.portMapper().register(PortMapper.SVC_EEPSITE, host, port); + } } } } diff --git a/apps/jetty/java/src/org/mortbay/servlet/MultiPartRequest.java b/apps/jetty/java/src/org/mortbay/servlet/MultiPartRequest.java index 0091d5d8a..566b7d3e1 100644 --- a/apps/jetty/java/src/org/mortbay/servlet/MultiPartRequest.java +++ b/apps/jetty/java/src/org/mortbay/servlet/MultiPartRequest.java @@ -49,6 +49,7 @@ import org.mortbay.util.LineInput; * * * Modded to compile with Jetty 6 for I2P + * Modded to make fields private and final * * @version $Id: MultiPartRequest.java,v 1.16 2005/12/02 20:13:52 gregwilkins Exp $ * @author Greg Wilkins @@ -59,14 +60,14 @@ public class MultiPartRequest //private static Log log = LogFactory.getLog(MultiPartRequest.class); /* ------------------------------------------------------------ */ - HttpServletRequest _request; - LineInput _in; - String _boundary; - String _encoding; - byte[] _byteBoundary; - MultiMap _partMap = new MultiMap(10); - int _char=-2; - boolean _lastPart=false; + private final HttpServletRequest _request; + private final LineInput _in; + private final String _boundary; + private final String _encoding; + private final byte[] _byteBoundary; + private final MultiMap _partMap = new MultiMap(10); + private int _char=-2; + private boolean _lastPart=false; /* ------------------------------------------------------------ */ /** Constructor. diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-continuation-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-continuation-8.1.21.v20160908.jar deleted file mode 100644 index ef8b5f4dc..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-continuation-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-deploy-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-deploy-8.1.21.v20160908.jar deleted file mode 100644 index 1df83b801..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-deploy-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-http-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-http-8.1.21.v20160908.jar deleted file mode 100644 index 6bbb4224c..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-http-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-io-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-io-8.1.21.v20160908.jar deleted file mode 100644 index 502691d37..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-io-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-jmx-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-jmx-8.1.21.v20160908.jar deleted file mode 100644 index 9a5a03090..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-jmx-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-rewrite-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-rewrite-8.1.21.v20160908.jar deleted file mode 100644 index a2a145408..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-rewrite-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-security-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-security-8.1.21.v20160908.jar deleted file mode 100644 index dcacc12ad..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-security-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-server-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-server-8.1.21.v20160908.jar deleted file mode 100644 index 0a99f8497..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-server-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-servlet-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-servlet-8.1.21.v20160908.jar deleted file mode 100644 index 1e563538c..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-servlet-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-servlets-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-servlets-8.1.21.v20160908.jar deleted file mode 100644 index 28e4fe6c2..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-servlets-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-util-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-util-8.1.21.v20160908.jar deleted file mode 100644 index 646b23b0f..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-util-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-webapp-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-webapp-8.1.21.v20160908.jar deleted file mode 100644 index b8d340f32..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-webapp-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-xml-8.1.21.v20160908.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-xml-8.1.21.v20160908.jar deleted file mode 100644 index 1bedc9e62..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jetty-xml-8.1.21.v20160908.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jsp/javax.servlet.jsp-2.2.0.v201112011158.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jsp/javax.servlet.jsp-2.2.0.v201112011158.jar deleted file mode 100644 index dc2fbb0da..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/jsp/javax.servlet.jsp-2.2.0.v201112011158.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/servlet-api-3.0.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/servlet-api-3.0.jar deleted file mode 100644 index b13540968..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/lib/servlet-api-3.0.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-8.1.21.v20160908/start.jar b/apps/jetty/jetty-distribution-8.1.21.v20160908/start.jar deleted file mode 100644 index 43442c445..000000000 Binary files a/apps/jetty/jetty-distribution-8.1.21.v20160908/start.jar and /dev/null differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/apache-jsp/org.eclipse.jetty.apache-jsp-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/apache-jsp/org.eclipse.jetty.apache-jsp-9.2.21.v20170120.jar new file mode 100644 index 000000000..5a6cc108e Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/apache-jsp/org.eclipse.jetty.apache-jsp-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/apache-jsp/org.mortbay.jasper.apache-el-8.0.33.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/apache-jsp/org.mortbay.jasper.apache-el-8.0.33.jar new file mode 100644 index 000000000..32a98131e Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/apache-jsp/org.mortbay.jasper.apache-el-8.0.33.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/apache-jsp/org.mortbay.jasper.apache-jsp-8.0.33.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/apache-jsp/org.mortbay.jasper.apache-jsp-8.0.33.jar new file mode 100644 index 000000000..3577b5a06 Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/apache-jsp/org.mortbay.jasper.apache-jsp-8.0.33.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-continuation-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-continuation-9.2.21.v20170120.jar new file mode 100644 index 000000000..4aac6404c Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-continuation-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-deploy-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-deploy-9.2.21.v20170120.jar new file mode 100644 index 000000000..d535a6b2e Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-deploy-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-http-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-http-9.2.21.v20170120.jar new file mode 100644 index 000000000..507952734 Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-http-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-io-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-io-9.2.21.v20170120.jar new file mode 100644 index 000000000..5eafefc7c Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-io-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-jmx-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-jmx-9.2.21.v20170120.jar new file mode 100644 index 000000000..5f11c2fd4 Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-jmx-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-rewrite-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-rewrite-9.2.21.v20170120.jar new file mode 100644 index 000000000..ea2adc1f8 Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-rewrite-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-schemas-3.1.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-schemas-3.1.jar new file mode 100644 index 000000000..7166da3f8 Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-schemas-3.1.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-security-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-security-9.2.21.v20170120.jar new file mode 100644 index 000000000..f54d07c38 Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-security-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-server-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-server-9.2.21.v20170120.jar new file mode 100644 index 000000000..8a4c5fad4 Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-server-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-servlet-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-servlet-9.2.21.v20170120.jar new file mode 100644 index 000000000..4fd13be32 Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-servlet-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-servlets-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-servlets-9.2.21.v20170120.jar new file mode 100644 index 000000000..c11519c1a Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-servlets-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-util-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-util-9.2.21.v20170120.jar new file mode 100644 index 000000000..edf6b9bea Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-util-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-webapp-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-webapp-9.2.21.v20170120.jar new file mode 100644 index 000000000..a7f7dcd76 Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-webapp-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-xml-9.2.21.v20170120.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-xml-9.2.21.v20170120.jar new file mode 100644 index 000000000..f82eba9cf Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/jetty-xml-9.2.21.v20170120.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/servlet-api-3.1.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/servlet-api-3.1.jar new file mode 100644 index 000000000..6b14c3d26 Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/lib/servlet-api-3.1.jar differ diff --git a/apps/jetty/jetty-distribution-9.2.21.v20170120/start.jar b/apps/jetty/jetty-distribution-9.2.21.v20170120/start.jar new file mode 100644 index 000000000..4dc45c3b9 Binary files /dev/null and b/apps/jetty/jetty-distribution-9.2.21.v20170120/start.jar differ diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocket.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocket.java index 7c7c0b19c..d38afc689 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocket.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocket.java @@ -112,6 +112,20 @@ public interface I2PSocket extends Closeable { * @since 0.8.9 */ public int getLocalPort(); + + /** + * Resets and closes this socket. Sends a RESET indication to the far-end. + * This is the equivalent of setSoLinger(true, 0) followed by close() on a Java Socket. + * + * Nonblocking. + * Any thread currently blocked in an I/O operation upon this socket will throw an IOException. + * Once a socket has been reset, it is not available for further networking use + * (i.e. can't be reconnected or rebound). A new socket needs to be created. + * Resetting this socket will also close the socket's InputStream and OutputStream. + * + * @since 0.9.30 + */ + public void reset() throws IOException; /** * Deprecated, unimplemented, does nothing. Original description: diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManager.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManager.java index ab183a817..e8638d4a3 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManager.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManager.java @@ -38,6 +38,10 @@ public interface I2PSocketManager { public I2PSession getSession(); /** + * For a server, you must call connect() on the returned object. + * Connecting the primary session does NOT connect any subsessions. + * If the primary session is not connected, connecting a subsession will connect the primary session first. + * * @return a new subsession, non-null * @param privateKeyStream null for transient, if non-null must have same encryption keys as primary session * and different signing keys diff --git a/apps/routerconsole/java/build.xml b/apps/routerconsole/java/build.xml index 56c399b98..d5385d871 100644 --- a/apps/routerconsole/java/build.xml +++ b/apps/routerconsole/java/build.xml @@ -70,11 +70,15 @@ + + + +
@@ -362,17 +366,22 @@ ** -trimSpaces Trim spaces in template text between actions, directives --> + + - + + - - + + + + @@ -403,10 +412,13 @@ - + + - + + + diff --git a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java index 44847b1b9..bd5525df2 100644 --- a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java @@ -457,6 +457,13 @@ class PluginUpdateRunner extends UpdateRunner { statusDone("" + _t("Plugin requires Jetty version {0} or higher", minVersion) + ""); return; } + String blacklistVersion = PluginStarter.jetty9Blacklist.get(appName); + if (blacklistVersion != null && + VersionComparator.comp(version, blacklistVersion) <= 0) { + to.delete(); + statusDone("" + _t("Plugin requires Jetty version {0} or lower", "8.9999") + ""); + return; + } maxVersion = ConfigClientsHelper.stripHTML(props, "max-jetty-version"); if (maxVersion != null && VersionComparator.comp(maxVersion, oldVersion) < 0) { @@ -480,7 +487,7 @@ class PluginUpdateRunner extends UpdateRunner { } // we don't need the original file anymore. to.delete(); - statusDone("" + _t("Plugin will be installed on next restart.") + ""); + statusDone("" + _t("Plugin will be installed on next restart.") + ' ' + appName + ' ' + version + ""); return; } if (PluginStarter.isPluginRunning(appName, _context)) { @@ -498,7 +505,7 @@ class PluginUpdateRunner extends UpdateRunner { } else { if (Boolean.valueOf(props.getProperty("update-only")).booleanValue()) { to.delete(); - statusDone("" + _t("Plugin is for upgrades only, but the plugin is not installed") + ""); + statusDone("" + _t("Plugin is for upgrades only, but the plugin is not installed") + ". " + appName + ' ' + version + ""); return; } if (!destDir.mkdir()) { @@ -518,7 +525,7 @@ class PluginUpdateRunner extends UpdateRunner { to.delete(); // install != update. Changing the user's settings like this is probabbly a bad idea. if (Boolean.valueOf( props.getProperty("dont-start-at-install")).booleanValue()) { - statusDone("" + _t("Plugin {0} installed", appName) + ""); + statusDone("" + _t("Plugin {0} installed", appName + ' ' + version) + ""); if(!update) { Properties pluginProps = PluginStarter.pluginProperties(); pluginProps.setProperty(PluginStarter.PREFIX + appName + PluginStarter.ENABLED, "false"); @@ -534,19 +541,19 @@ class PluginUpdateRunner extends UpdateRunner { String linkURL = ConfigClientsHelper.stripHTML(props, "consoleLinkURL"); String link; if (linkName != null && linkURL != null) - link = "" + linkName + ""; + link = "" + linkName + ' ' + version + ""; else - link = appName; + link = appName + ' ' + version; statusDone("" + _t("Plugin {0} installed and started", link) + ""); } else - statusDone("" + _t("Plugin {0} installed but failed to start, check logs", appName) + ""); + statusDone("" + _t("Plugin {0} installed but failed to start, check logs", appName + ' ' + version) + ""); } catch (Throwable e) { - statusDone("" + _t("Plugin {0} installed but failed to start", appName) + ": " + e + ""); - _log.error("Error starting plugin " + appName, e); + statusDone("" + _t("Plugin {0} installed but failed to start", appName + ' ' + version) + ": " + e + ""); + _log.error("Error starting plugin " + appName + ' ' + version, e); } } else { - statusDone("" + _t("Plugin {0} installed", appName) + ""); + statusDone("" + _t("Plugin {0} installed", appName + ' ' + version) + ""); } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/CodedIconRendererServlet.java b/apps/routerconsole/java/src/net/i2p/router/web/CodedIconRendererServlet.java index c2be82a9d..f85c3d488 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/CodedIconRendererServlet.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/CodedIconRendererServlet.java @@ -33,7 +33,7 @@ public class CodedIconRendererServlet extends HttpServlet { @Override - protected void service(HttpServletRequest srq, HttpServletResponse srs) throws ServletException, IOException { + protected void doGet(HttpServletRequest srq, HttpServletResponse srs) throws ServletException, IOException { byte[] data; String name = srq.getParameter("plugin"); data = NavHelper.getBinary(name); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java index 4cdf13e35..a40c64531 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java @@ -573,7 +573,18 @@ public class ConfigClientsHandler extends FormHandler { private void startPlugin(String app) { try { PluginStarter.startPlugin(_context, app); - addFormNotice(_t("Started plugin {0}", app)); + // linkify the app name for the message if available + Properties props = PluginStarter.pluginProperties(_context, app); + String name = ConfigClientsHelper.stripHTML(props, "consoleLinkName_" + Messages.getLanguage(_context)); + if (name == null) + name = ConfigClientsHelper.stripHTML(props, "consoleLinkName"); + String url = ConfigClientsHelper.stripHTML(props, "consoleLinkURL"); + if (name != null && url != null && name.length() > 0 && url.length() > 0) { + app = "" + name + ""; + addFormNoticeNoEscape(_t("Started plugin {0}", app)); + } else { + addFormNotice(_t("Started plugin {0}", app)); + } } catch (Throwable e) { addFormError(_t("Error starting plugin {0}", app) + ": " + e); _log.error("Error starting plugin " + app, e); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java index 9c9c7e9dd..c33f0e926 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java @@ -67,6 +67,22 @@ public class PluginStarter implements Runnable { private static Map _clCache = new ConcurrentHashMap(); private static Map> pluginWars = new ConcurrentHashMap>(); + /** + * Plugin name to plugin version of plugins that do not work + * with Jetty 9, but do not have a max-jetty-version=8.9999 set. + * Unmodifiable. + * + * @since 0.9.30 + */ + public static final Map jetty9Blacklist; + + static { + Map map = new HashMap(4); + map.put("i2pbote", "0.4.5"); + map.put("BwSchedule", "0.0.36"); + jetty9Blacklist = Collections.unmodifiableMap(map); + } + public PluginStarter(RouterContext ctx) { _context = ctx; } @@ -297,8 +313,8 @@ public class PluginStarter implements Runnable { Properties props = pluginProperties(ctx, appName); - - + // For the following, we use the exact same translated strings as in PluginUpdateRunner + // to avoid duplication String minVersion = ConfigClientsHelper.stripHTML(props, "min-i2p-version"); if (minVersion != null && @@ -306,6 +322,7 @@ public class PluginStarter implements Runnable { String foo = "Plugin " + appName + " requires I2P version " + minVersion + " or higher"; log.error(foo); disablePlugin(appName); + foo = gettext("This plugin requires I2P version {0} or higher", minVersion, ctx); throw new Exception(foo); } @@ -315,6 +332,7 @@ public class PluginStarter implements Runnable { String foo = "Plugin " + appName + " requires Java version " + minVersion + " or higher"; log.error(foo); disablePlugin(appName); + foo = gettext("This plugin requires Java version {0} or higher", minVersion, ctx); throw new Exception(foo); } @@ -325,6 +343,18 @@ public class PluginStarter implements Runnable { String foo = "Plugin " + appName + " requires Jetty version " + minVersion + " or higher"; log.error(foo); disablePlugin(appName); + foo = gettext("Plugin requires Jetty version {0} or higher", minVersion, ctx); + throw new Exception(foo); + } + + String blacklistVersion = jetty9Blacklist.get(appName); + String curVersion = ConfigClientsHelper.stripHTML(props, "version"); + if (blacklistVersion != null && + VersionComparator.comp(curVersion, blacklistVersion) <= 0) { + String foo = "Plugin " + appName + " requires Jetty version 8.9999 or lower"; + log.error(foo); + disablePlugin(appName); + foo = gettext("Plugin requires Jetty version {0} or lower", "8.9999", ctx); throw new Exception(foo); } @@ -334,6 +364,7 @@ public class PluginStarter implements Runnable { String foo = "Plugin " + appName + " requires Jetty version " + maxVersion + " or lower"; log.error(foo); disablePlugin(appName); + foo = gettext("Plugin requires Jetty version {0} or lower", maxVersion, ctx); throw new Exception(foo); } @@ -1002,6 +1033,14 @@ public class PluginStarter implements Runnable { method.invoke(urlClassLoader, new Object[]{u}); } + /** + * translate a string + * @since 0.9.30 + */ + private static String gettext(String s, Object o, I2PAppContext ctx) { + return Messages.getString(s, o, ctx); + } + /** translate a string */ private static String ngettext(String s, String p, int n, I2PAppContext ctx) { return Messages.getString(n, s, p, ctx); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 888ccb131..5aefb1588 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -17,9 +17,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.SortedSet; import java.util.StringTokenizer; -import java.util.Timer; import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; @@ -46,19 +46,21 @@ import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.authentication.DigestAuthenticator; import org.eclipse.jetty.server.AbstractConnector; +import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.NCSARequestLog; +import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.UserIdentity; -import org.eclipse.jetty.server.bio.SocketConnector; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.server.handler.RequestLogHandler; -import org.eclipse.jetty.server.nio.SelectChannelConnector; -import org.eclipse.jetty.server.ssl.SslSocketConnector; -import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.webapp.WebAppContext; @@ -70,6 +72,7 @@ import org.eclipse.jetty.util.security.Credential.MD5; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.ExecutorThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; import org.eclipse.jetty.util.thread.ThreadPool; import org.tanukisoftware.wrapper.WrapperManager; @@ -96,7 +99,7 @@ public class RouterConsoleRunner implements RouterApp { private final ClientAppManager _mgr; private volatile ClientAppState _state = UNINITIALIZED; private static Server _server; - private static Timer _jettyTimer; + private static ScheduledExecutorScheduler _jettyTimer; private String _listenPort; private String _listenHost; private String _sslListenPort; @@ -231,6 +234,7 @@ public class RouterConsoleRunner implements RouterApp { changeState(STOPPING); if (PluginStarter.pluginsEnabled(_context)) (new I2PAppThread(new PluginStopper(_context), "PluginStopper")).start(); + stopAllWebApps(); try { _server.stop(); } catch (Exception ie) {} @@ -239,7 +243,9 @@ public class RouterConsoleRunner implements RouterApp { portMapper.unregister(PortMapper.SVC_HTTPS_CONSOLE); synchronized(RouterConsoleRunner.class) { if (_jettyTimer != null) { - _jettyTimer.cancel(); + try { + _jettyTimer.stop(); + } catch (Exception e) {} _jettyTimer = null; } } @@ -363,6 +369,22 @@ public class RouterConsoleRunner implements RouterApp { * DefaultHandler * RequestLogHandler (opt) * + * + * Porting to Jetty 9: + * + * http://dev.eclipse.org/mhonarc/lists/jetty-dev/msg01952.html + * You are missing a few facts about Jetty 9.1 ... + * First, there are no longer any blocking connectors. + * Its all async / nio connectors now. (mainly because that's the direction that the servlet api 3.1 is taking) + * + * Next, there is only 1 connector. The ServerConnector. + * However, it takes 1 or more ConnectionFactory implementations to know how to handle the incoming connection. + * We have factories for HTTP (0.9 thru 1.1), SPDY, SSL-http, and SSL-npn so far. + * This list of factories will expand as the future of connectivity to web servers is ever growing (think HTTP/2) + * + * Use the embedded examples for help understanding this. + * http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java?id=jetty-9.1.0.RC0 + * */ public void startConsole() { File workDir = new SecureDirectory(_context.getTempDir(), "jetty-work"); @@ -375,8 +397,9 @@ public class RouterConsoleRunner implements RouterApp { // so Jetty can find WebAppConfiguration System.setProperty("jetty.class.path", _context.getBaseDir() + "/lib/routerconsole.jar"); - _server = new Server(); - _server.setGracefulShutdown(1000); + // FIXME + // http://dev.eclipse.org/mhonarc/lists/jetty-users/msg03487.html + //_server.setGracefulShutdown(1000); // In Jetty 6, QTP was not concurrent, so we switched to // ThreadPoolExecutor with a fixed-size queue, a set maxThreads, @@ -408,14 +431,11 @@ public class RouterConsoleRunner implements RouterApp { // class not found... //System.out.println("INFO: Jetty concurrent ThreadPool unavailable, using QueuedThreadPool"); LinkedBlockingQueue lbq = new LinkedBlockingQueue(4*MAX_THREADS); - QueuedThreadPool qtp = new QueuedThreadPool(lbq); - // min and max threads will be set below - //qtp.setMinThreads(MIN_THREADS); - //qtp.setMaxThreads(MAX_THREADS); - qtp.setMaxIdleTimeMs(MAX_IDLE_TIME); + // min and max threads will be reset below + QueuedThreadPool qtp = new QueuedThreadPool(MAX_THREADS, MIN_THREADS, MAX_IDLE_TIME, lbq); qtp.setName(THREAD_NAME); qtp.setDaemon(true); - _server.setThreadPool(qtp); + _server = new Server(qtp); //} HandlerCollection hColl = new HandlerCollection(); @@ -502,27 +522,15 @@ public class RouterConsoleRunner implements RouterApp { } finally { if (testSock != null) try { testSock.close(); } catch (IOException ioe) {} } - //if (host.indexOf(":") >= 0) // IPV6 - requires patched Jetty 5 - // _server.addListener('[' + host + "]:" + _listenPort); - //else - // _server.addListener(host + ':' + _listenPort); - AbstractConnector lsnr; - if (SystemVersion.isJava6() && !SystemVersion.isGNU()) { - SelectChannelConnector slsnr = new SelectChannelConnector(); - slsnr.setUseDirectBuffers(false); // default true seems to be leaky - lsnr = slsnr; - } else { - // Jetty 6 and NIO on Java 5 don't get along that well - // Also: http://jira.codehaus.org/browse/JETTY-1238 - // "Do not use GCJ with Jetty, it will not work." - // Actually it does if you don't use NIO - lsnr = new SocketConnector(); - } + HttpConfiguration httpConfig = new HttpConfiguration(); + // number of acceptors, (default) number of selectors + ServerConnector lsnr = new ServerConnector(_server, 1, 0, + new HttpConnectionFactory(httpConfig)); + //lsnr.setUseDirectBuffers(false); // default true seems to be leaky lsnr.setHost(host); lsnr.setPort(lport); - lsnr.setMaxIdleTime(90*1000); // default 10 sec + lsnr.setIdleTimeout(90*1000); // default 10 sec lsnr.setName("ConsoleSocket"); // all with same name will use the same thread pool - lsnr.setAcceptors(1); // default changed to 2 somewhere in Jetty 7? //_server.addConnector(lsnr); connectors.add(lsnr); boundAddresses++; @@ -586,22 +594,19 @@ public class RouterConsoleRunner implements RouterApp { } finally { if (testSock != null) try { testSock.close(); } catch (IOException ioe) {} } - // TODO if class not found use SslChannelConnector - AbstractConnector ssll; - if (SystemVersion.isJava6() && !SystemVersion.isGNU()) { - SslSelectChannelConnector sssll = new SslSelectChannelConnector(sslFactory); - sssll.setUseDirectBuffers(false); // default true seems to be leaky - ssll = sssll; - } else { - // Jetty 6 and NIO on Java 5 don't get along that well - SslSocketConnector sssll = new SslSocketConnector(sslFactory); - ssll = sssll; - } + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setSecureScheme("https"); + httpConfig.setSecurePort(sslPort); + httpConfig.addCustomizer(new SecureRequestCustomizer()); + // number of acceptors, (default) number of selectors + ServerConnector ssll = new ServerConnector(_server, 1, 0, + new SslConnectionFactory(sslFactory, "http/1.1"), + new HttpConnectionFactory(httpConfig)); + //sssll.setUseDirectBuffers(false); // default true seems to be leaky ssll.setHost(host); ssll.setPort(sslPort); - ssll.setMaxIdleTime(90*1000); // default 10 sec + ssll.setIdleTimeout(90*1000); // default 10 sec ssll.setName("ConsoleSocket"); // all with same name will use the same thread pool - ssll.setAcceptors(1); // default changed to 2 somewhere in Jetty 7? //_server.addConnector(ssll); connectors.add(ssll); boundAddresses++; @@ -638,17 +643,25 @@ public class RouterConsoleRunner implements RouterApp { File tmpdir = new SecureDirectory(workDir, ROUTERCONSOLE + "-" + (_listenPort != null ? _listenPort : _sslListenPort)); tmpdir.mkdir(); - if (!SystemVersion.isWindows() && !SystemVersion.isMac() && - _context.getBaseDir().getAbsolutePath().equals("/usr/share/i2p")) { - // We are using Tomcat 6, so the Debian patch doesn't apply. - // Remove when we switch to Tomcat 8 - _context.logManager().getLog(Server.class).logAlways(net.i2p.util.Log.INFO, "Please ignore any InstanceManager warnings"); - } rootServletHandler = new ServletHandler(); rootWebApp = new LocaleWebAppHandler(_context, "/", _webAppsDir + ROUTERCONSOLE + ".war", tmpdir, rootServletHandler); - initialize(_context, (WebAppContext)(rootWebApp.getHandler())); + try { + // Not sure who is supposed to call this, but unless we do, + // all the jsps die NPE, because JspFactory.getDefaultContext() returns null. + // We probably have to do this because we don't bundle the Jetty annotations jar and scanner. + // This is only with Tomcat 8, not with the Jetty (Eclipse) jsp impl. + // Got a clue from this ancient post for Tomcat 6: + // https://bz.apache.org/bugzilla/show_bug.cgi?id=39804 + // see also apps/jetty/build.xml + Class.forName("org.eclipse.jetty.apache.jsp.JettyJasperInitializer"); + } catch (ClassNotFoundException cnfe) { + System.err.println("Warning: JettyJasperInitializer not found"); + } + WebAppContext wac = (WebAppContext)(rootWebApp.getHandler()); + initialize(_context, wac); + WebAppStarter.setWebAppConfiguration(wac); chColl.addHandler(rootWebApp); } catch (Exception ioe) { @@ -709,7 +722,13 @@ public class RouterConsoleRunner implements RouterApp { for (int i = 0; i < fileNames.length; i++) { String appName = fileNames[i].substring(0, fileNames[i].lastIndexOf(".war")); String enabled = props.getProperty(PREFIX + appName + ENABLED); - if (! "false".equals(enabled)) { + if (appName.equals("addressbook")) { + // addressbook.war is now empty, thread is started by SusiDNS + if (enabled != null) { + props.remove(PREFIX + "addressbook" + ENABLED); + rewrite = true; + } + } else if (! "false".equals(enabled)) { try { String path = new File(dir, fileNames[i]).getCanonicalPath(); WebAppStarter.startWebApp(_context, chColl, appName, path); @@ -945,7 +964,12 @@ public class RouterConsoleRunner implements RouterApp { // see HashSessionManager javadoc synchronized(RouterConsoleRunner.class) { if (_jettyTimer == null) { - _jettyTimer = new Timer("Console HashSessionScavenger", true); + _jettyTimer = new ScheduledExecutorScheduler("Console HashSessionScavenger", true); + try { + _jettyTimer.start(); + } catch (Exception e) { + System.err.println("Warning: ScheduledExecutorScheduler start failed: " + e); + } } context.getServletContext().setAttribute("org.eclipse.jetty.server.session.timer", _jettyTimer); } @@ -1018,6 +1042,32 @@ public class RouterConsoleRunner implements RouterApp { } } + /** + * Stops all but the root webapp (routerconsole.war) + * In Jetty 9, stopping the server doesn't stop the non-root webapps, + * so we must do it here. + * There should be a better way to do this, possibly by + * making the webapps "managed". + * @since 0.9.30 + */ + private void stopAllWebApps() { + Properties props = webAppProperties(_context); + Set keys = props.stringPropertyNames(); + for (String name : keys) { + if (name.startsWith(PREFIX) && name.endsWith(ENABLED)) { + String app = name.substring(PREFIX.length(), name.lastIndexOf(ENABLED)); + if (ROUTERCONSOLE.equals(app)) + continue; + if (WebAppStarter.isWebAppRunning(app)) { + try { + WebAppStarter.stopWebApp(app); + } catch (Throwable t) { t.printStackTrace(); } + } + } + } + + } + static class WarFilenameFilter implements FilenameFilter { private static final WarFilenameFilter _filter = new WarFilenameFilter(); public static WarFilenameFilter instance() { return _filter; } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/WebAppConfiguration.java b/apps/routerconsole/java/src/net/i2p/router/web/WebAppConfiguration.java index 3e2eb9ecc..f79533d5e 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/WebAppConfiguration.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/WebAppConfiguration.java @@ -15,6 +15,7 @@ import java.util.StringTokenizer; import net.i2p.I2PAppContext; +import org.apache.tomcat.SimpleInstanceManager; import org.eclipse.jetty.webapp.Configuration; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; @@ -97,7 +98,11 @@ public class WebAppConfiguration implements Configuration { // Ticket #957... don't know why... // Only really required if started manually, but we don't know that from here cp = "jetty-util.jar"; - } else ****/ if (pluginDir.exists()) { +****/ + if (ctxPath.equals("/susidns")) { + // Old installs don't have this in their wrapper.config classpath + cp = "addressbook.jar"; + } else if (pluginDir.exists()) { File consoleDir = new File(pluginDir, "console"); Properties props = RouterConsoleRunner.webAppProperties(consoleDir.getAbsolutePath()); cp = props.getProperty(RouterConsoleRunner.PREFIX + appName + CLASSPATH); @@ -173,11 +178,13 @@ public class WebAppConfiguration implements Configuration { } } else { // Java 9 - assume everything in lib/ is in the classpath + // except addressbook.jar File libDir = new File(ctx.getBaseDir(), "lib"); File[] files = libDir.listFiles(); if (files != null) { for (int i = 0; i < files.length; i++) { - if (files[i].getName().endsWith(".jar")) + String name = files[i].getName(); + if (name.endsWith(".jar") && !name.equals("addressbook.jar")) rv.add(files[i].toURI()); } } @@ -191,6 +198,10 @@ public class WebAppConfiguration implements Configuration { /** @since Jetty 7 */ public void configure(WebAppContext context) throws Exception { configureClassPath(context); + // do we just need one, in the ContextHandlerCollection, or one for each? + // http://stackoverflow.com/questions/17529936/issues-while-using-jetty-embedded-to-handle-jsp-jasperexception-unable-to-com + // https://github.com/jetty-project/embedded-jetty-jsp/blob/master/src/main/java/org/eclipse/jetty/demo/Main.java + context.getServletContext().setAttribute("org.apache.tomcat.InstanceManager", new SimpleInstanceManager()); } /** @since Jetty 7 */ diff --git a/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java index 70a86a89b..884a88cbc 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java @@ -109,17 +109,30 @@ public class WebAppStarter { // this does the passwords... RouterConsoleRunner.initialize(ctx, wac); + setWebAppConfiguration(wac); + server.addHandler(wac); + server.mapContexts(); + return wac; + } + /** + * @since Jetty 9 + */ + static void setWebAppConfiguration(WebAppContext wac) { // see WebAppConfiguration for info String[] classNames = wac.getConfigurationClasses(); + // In Jetty 9, it doesn't set the defaults if we've already added one, but the + // defaults aren't set yet when we call the above. So we have to get the defaults. + // Without the default configuration, the web.xml isn't read, and the webapp + // won't respond to any requests, even though it appears to be running. + // See WebAppContext.loadConfigurations() in source + if (classNames.length == 0) + classNames = wac.getDefaultConfigurationClasses(); String[] newClassNames = new String[classNames.length + 1]; for (int j = 0; j < classNames.length; j++) newClassNames[j] = classNames[j]; newClassNames[classNames.length] = WebAppConfiguration.class.getName(); wac.setConfigurationClasses(newClassNames); - server.addHandler(wac); - server.mapContexts(); - return wac; } /** diff --git a/apps/routerconsole/jsp/viewstat.jsp b/apps/routerconsole/jsp/viewstat.jsp index ce44c5cb7..2d17dedc7 100644 --- a/apps/routerconsole/jsp/viewstat.jsp +++ b/apps/routerconsole/jsp/viewstat.jsp @@ -61,13 +61,13 @@ if ( !rendered && ((rs != null) || fakeBw) ) { if (str != null) try { periodCount = Integer.parseInt(str); } catch (NumberFormatException nfe) {} str = request.getParameter("end"); if (str != null) try { end = Integer.parseInt(str); } catch (NumberFormatException nfe) {} - boolean hideLegend = Boolean.parseBoolean((String) request.getParameter("hideLegend")); - boolean hideGrid = Boolean.parseBoolean((String) request.getParameter("hideGrid")); - boolean hideTitle = Boolean.parseBoolean((String) request.getParameter("hideTitle")); - boolean showEvents = Boolean.parseBoolean((String) request.getParameter("showEvents")); + boolean hideLegend = Boolean.parseBoolean(request.getParameter("hideLegend")); + boolean hideGrid = Boolean.parseBoolean(request.getParameter("hideGrid")); + boolean hideTitle = Boolean.parseBoolean(request.getParameter("hideTitle")); + boolean showEvents = Boolean.parseBoolean(request.getParameter("showEvents")); boolean showCredit = false; if (request.getParameter("showCredit") != null) - showCredit = Boolean.parseBoolean((String) request.getParameter("showCredit")); + showCredit = Boolean.parseBoolean(request.getParameter("showCredit")); if (fakeBw) rendered = net.i2p.router.web.StatSummarizer.instance().renderRatePng(cout, width, height, hideLegend, hideGrid, hideTitle, showEvents, periodCount, end, showCredit); else diff --git a/apps/sam/java/src/net/i2p/sam/MasterSession.java b/apps/sam/java/src/net/i2p/sam/MasterSession.java index fc69a7238..19d102951 100644 --- a/apps/sam/java/src/net/i2p/sam/MasterSession.java +++ b/apps/sam/java/src/net/i2p/sam/MasterSession.java @@ -389,7 +389,7 @@ class MasterSession extends SAMv3StreamSession implements SAMDatagramReceiver, S boolean ok = ssess.queueSocket(i2ps); if (!ok) { _log.logAlways(Log.WARN, "Accept queue overflow for " + ssess); - try { i2ps.close(); } catch (IOException ioe) {} + try { i2ps.reset(); } catch (IOException ioe) {} } } else { if (_log.shouldLog(Log.WARN)) diff --git a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java index 8cc9b5662..3d6708e05 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java @@ -564,7 +564,7 @@ class SAMStreamSession implements SAMMessageSess { int id = createSocketHandler(i2ps, 0); if (id == 0) { _log.error("SAM STREAM session handler not created!"); - i2ps.close(); + i2ps.reset(); continue; } diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java index 8be28b475..40f3b95df 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java @@ -383,7 +383,7 @@ class SAMv3StreamSession extends SAMStreamSession implements Session Log log = ctx.logManager().getLog(SAMv3StreamSession.class); log.error("SSL error", gse); try { - i2ps.close(); + i2ps.reset(); } catch (IOException ee) {} throw new RuntimeException("SSL error", gse); } @@ -401,7 +401,7 @@ class SAMv3StreamSession extends SAMStreamSession implements Session if (log.shouldLog(Log.WARN)) log.warn("Error forwarding", ioe); try { - i2ps.close(); + i2ps.reset(); } catch (IOException ee) {} continue; } @@ -433,7 +433,7 @@ class SAMv3StreamSession extends SAMStreamSession implements Session clientServerSock.close(); } catch (IOException ee) {} try { - i2ps.close(); + i2ps.reset(); } catch (IOException ee) {} continue ; } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketFull.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketFull.java index 6800e8a6e..f86a1979d 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketFull.java @@ -67,6 +67,32 @@ class I2PSocketFull implements I2PSocket { destroy(); } + /** + * Resets and closes this socket. Sends a RESET indication to the far-end. + * This is the equivalent of setSoLinger(true, 0) followed by close() on a Java Socket. + * + * Nonblocking. + * Any thread currently blocked in an I/O operation upon this socket will throw an IOException. + * Once a socket has been reset, it is not available for further networking use + * (i.e. can't be reconnected or rebound). A new socket needs to be created. + * Resetting this socket will also close the socket's InputStream and OutputStream. + * + * @since 0.9.30 + */ + public void reset() throws IOException { + Connection c = _connection; + if (c == null) return; + if (log.shouldLog(Log.INFO)) + log.info("reset() called, connected? " + c.getIsConnected() + " : " + c, new Exception()); + if (c.getIsConnected()) { + c.disconnect(false); + // this will cause any thread waiting in Connection.packetSendChoke() + // to throw an IOE + c.windowAdjusted(); + } + destroy(); + } + Connection getConnection() { return _connection; } /** diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java index 94a4c4ae6..ee4c4ecc7 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java @@ -236,6 +236,10 @@ public class I2PSocketManagerFull implements I2PSocketManager { } /** + * For a server, you must call connect() on the returned object. + * Connecting the primary session does NOT connect any subsessions. + * If the primary session is not connected, connecting a subsession will connect the primary session first. + * * @return a new subsession, non-null * @param privateKeyStream null for transient, if non-null must have same encryption keys as primary session * and different signing keys diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/MessageOutputStream.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/MessageOutputStream.java index 72b2e0251..b8889c972 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/MessageOutputStream.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/MessageOutputStream.java @@ -30,7 +30,9 @@ class MessageOutputStream extends OutputStream { private final AtomicBoolean _closed = new AtomicBoolean(); private long _written; private int _writeTimeout; - private ByteCache _dataCache; + private final ByteCache _dataCache; + private final int _originalBufferSize; + private int _currentBufferSize; private final Flusher _flusher; private volatile long _lastBuffered; /** if we enqueue data but don't flush it in this period, flush it passively */ @@ -68,6 +70,8 @@ class MessageOutputStream extends OutputStream { DataReceiver receiver, int bufSize, int passiveFlushDelay) { super(); _dataCache = ByteCache.getInstance(128, bufSize); + _originalBufferSize = bufSize; + _currentBufferSize = bufSize; _context = ctx; _log = ctx.logManager().getLog(MessageOutputStream.class); _buf = _dataCache.acquire().getData(); // new byte[bufSize]; @@ -75,7 +79,7 @@ class MessageOutputStream extends OutputStream { _dataLock = new Object(); _writeTimeout = -1; _passiveFlushDelay = passiveFlushDelay; - _nextBufferSize = -1; + _nextBufferSize = 0; //_sendPeriodBeginTime = ctx.clock().now(); //_context.statManager().createRateStat("stream.sendBps", "How fast we pump data through the stream", "Stream", new long[] { 60*1000, 5*60*1000, 60*60*1000 }); _flusher = new Flusher(timer); @@ -92,7 +96,16 @@ class MessageOutputStream extends OutputStream { public int getWriteTimeout() { return _writeTimeout; } - public void setBufferSize(int size) { _nextBufferSize = size; } + /** + * Caller should enforce a sane minimum. + * + * @param size must be greater than 0, and smaller than or equal to bufSize in constructor + */ + public void setBufferSize(int size) { + if (size <= 0 || size > _originalBufferSize) + return; + _nextBufferSize = size; + } @Override public void write(byte b[]) throws IOException { @@ -115,8 +128,11 @@ class MessageOutputStream extends OutputStream { // this is the only method that *adds* to the _buf, and all // code that reads from it is synchronized synchronized (_dataLock) { + // To simplify the code, and avoid losing data from shrinking the max size, + // we only update max size when current buffer is empty + final int maxBuffer = (_valid == 0) ? locked_updateBufferSize() : _currentBufferSize; if (_buf == null) throw new IOException("closed (buffer went away)"); - if (_valid + remaining < _buf.length) { + if (_valid + remaining < maxBuffer) { // simply buffer the data, no flush System.arraycopy(b, cur, _buf, _valid, remaining); _valid += remaining; @@ -131,19 +147,17 @@ class MessageOutputStream extends OutputStream { // buffer whatever we can fit then flush, // repeating until we've pushed all of the // data through - int toWrite = _buf.length - _valid; + int toWrite = maxBuffer - _valid; System.arraycopy(b, cur, _buf, _valid, toWrite); remaining -= toWrite; cur += toWrite; - _valid = _buf.length; + _valid = maxBuffer; if (_log.shouldLog(Log.INFO)) _log.info("write() direct valid = " + _valid); ws = _dataReceiver.writeData(_buf, 0, _valid); _written += _valid; _valid = 0; throwAnyError(); - - locked_updateBufferSize(); } } if (ws != null) { @@ -207,17 +221,21 @@ class MessageOutputStream extends OutputStream { /** * If the other side requested we shrink our buffer, do so. * + * @return the current buffer size */ - private final void locked_updateBufferSize() { + private final int locked_updateBufferSize() { int size = _nextBufferSize; if (size > 0) { // update the buffer size to the requested amount - _dataCache.release(new ByteArray(_buf)); - _dataCache = ByteCache.getInstance(128, size); - ByteArray ba = _dataCache.acquire(); - _buf = ba.getData(); - _nextBufferSize = -1; + // No, never do this, to avoid ByteCache churn. + //_dataCache.release(new ByteArray(_buf)); + //_dataCache = ByteCache.getInstance(128, size); + //ByteArray ba = _dataCache.acquire(); + //_buf = ba.getData(); + _currentBufferSize = size; + _nextBufferSize = 0; } + return _currentBufferSize; } /** @@ -273,7 +291,6 @@ class MessageOutputStream extends OutputStream { ws = _dataReceiver.writeData(_buf, 0, _valid); _written += _valid; _valid = 0; - locked_updateBufferSize(); _dataLock.notifyAll(); sent = true; } @@ -336,7 +353,6 @@ class MessageOutputStream extends OutputStream { ws = _dataReceiver.writeData(_buf, 0, _valid); _written += _valid; _valid = 0; - locked_updateBufferSize(); _dataLock.notifyAll(); } } @@ -409,7 +425,6 @@ class MessageOutputStream extends OutputStream { ba = new ByteArray(_buf); _buf = null; _valid = 0; - locked_updateBufferSize(); } _dataLock.notifyAll(); } @@ -494,7 +509,6 @@ class MessageOutputStream extends OutputStream { ws = target.writeData(_buf, 0, _valid); _written += _valid; _valid = 0; - locked_updateBufferSize(); _dataLock.notifyAll(); } long afterBuild = System.currentTimeMillis(); diff --git a/apps/susidns/src/WEB-INF/web-template.xml b/apps/susidns/src/WEB-INF/web-template.xml index 1937a5b14..a1b019ab7 100644 --- a/apps/susidns/src/WEB-INF/web-template.xml +++ b/apps/susidns/src/WEB-INF/web-template.xml @@ -13,6 +13,22 @@ susidns + + + addressbook-runner + net.i2p.addressbook.servlet.Servlet + + home + ./addressbook + + 1 + + + + addressbook-runner + /addressbook-runner + + diff --git a/apps/susidns/src/build.xml b/apps/susidns/src/build.xml index ac9a6f63a..60d66140f 100644 --- a/apps/susidns/src/build.xml +++ b/apps/susidns/src/build.xml @@ -16,20 +16,22 @@ --> - + + - - + + + - - + + diff --git a/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java b/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java index 9ea9eed4e..483cc47b6 100644 --- a/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java +++ b/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java @@ -314,6 +314,9 @@ public class AddressbookBean extends BaseBean } if (action.equals(_t("Delete Entry"))) search = null; + } else if (action.equals(_t("Add Alternate"))) { + // button won't be in UI + message = "Unsupported"; } if( changed ) { try { diff --git a/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java b/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java index e76b4a4dd..99a516581 100644 --- a/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java +++ b/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java @@ -231,7 +231,7 @@ public class NamingServiceBean extends AddressbookBean if (_context.getBooleanProperty(PROP_PW_ENABLE) || (serial != null && serial.equals(lastSerial))) { boolean changed = false; - if (action.equals(_t("Add")) || action.equals(_t("Replace"))) { + if (action.equals(_t("Add")) || action.equals(_t("Replace")) || action.equals(_t("Add Alternate"))) { if(hostname != null && destination != null) { try { // throws IAE with translated message @@ -243,20 +243,38 @@ public class NamingServiceBean extends AddressbookBean Destination oldDest = getNamingService().lookup(host, nsOptions, outProperties); if (oldDest != null && destination.equals(oldDest.toBase64())) { message = _t("Host name {0} is already in address book, unchanged.", displayHost); - } else if (oldDest != null && !action.equals(_t("Replace"))) { + } else if (oldDest == null && action.equals(_t("Add Alternate"))) { + message = _t("Host name {0} is not in the address book.", displayHost); + } else if (oldDest != null && action.equals(_t("Add"))) { message = _t("Host name {0} is already in address book with a different destination. Click \"Replace\" to overwrite.", displayHost); } else { try { Destination dest = new Destination(destination); if (oldDest != null) { nsOptions.putAll(outProperties); - nsOptions.setProperty("m", Long.toString(_context.clock().now())); + String now = Long.toString(_context.clock().now()); + if (action.equals(_t("Add Alternate"))) + nsOptions.setProperty("a", now); + else + nsOptions.setProperty("m", now); } nsOptions.setProperty("s", _t("Manually added via SusiDNS")); - boolean success = getNamingService().put(host, dest, nsOptions); + boolean success; + if (action.equals(_t("Add Alternate"))) { + // check all for dups + List all = getNamingService().lookupAll(host); + if (all == null || !all.contains(dest)) { + success = getNamingService().addDestination(host, dest, nsOptions); + } else { + // will get generic message below + success = false; + } + } else { + success = getNamingService().put(host, dest, nsOptions); + } if (success) { changed = true; - if (oldDest == null) + if (oldDest == null || action.equals(_t("Add Alternate"))) message = _t("Destination added for {0}.", displayHost); else message = _t("Destination changed for {0}.", displayHost); @@ -285,8 +303,21 @@ public class NamingServiceBean extends AddressbookBean } else if (action.equals(_t("Delete Selected")) || action.equals(_t("Delete Entry"))) { String name = null; int deleted = 0; + Destination matchDest = null; + if (action.equals(_t("Delete Entry"))) { + // remove specified dest only in case there is more than one + if (destination != null) { + try { + matchDest = new Destination(destination); + } catch (DataFormatException dfe) {} + } + } for (String n : deletionMarks) { - boolean success = getNamingService().remove(n, nsOptions); + boolean success; + if (matchDest != null) + success = getNamingService().remove(n, matchDest, nsOptions); + else + success = getNamingService().remove(n, nsOptions); String uni = AddressBean.toUnicode(n); String displayHost = uni.equals(n) ? n : uni + " (" + n + ')'; if (!success) { diff --git a/apps/addressbook/java/src/net/i2p/addressbook/Servlet.java b/apps/susidns/src/java/src/net/i2p/addressbook/servlet/Servlet.java similarity index 62% rename from apps/addressbook/java/src/net/i2p/addressbook/Servlet.java rename to apps/susidns/src/java/src/net/i2p/addressbook/servlet/Servlet.java index c72adef0a..108295005 100644 --- a/apps/addressbook/java/src/net/i2p/addressbook/Servlet.java +++ b/apps/susidns/src/java/src/net/i2p/addressbook/servlet/Servlet.java @@ -19,11 +19,10 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package net.i2p.addressbook; +package net.i2p.addressbook.servlet; import java.io.IOException; import java.io.PrintWriter; -import java.util.Random; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -31,25 +30,29 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import net.i2p.I2PAppContext; +//import net.i2p.addressbook.DaemonThread; +import net.i2p.util.Log; + /** * A wrapper for addressbook to allow it to be started as a web application. * - * This was a GenericServlet, we make it an HttpServlet solely to provide a hook - * for SusiDNS to wake us up when the subscription list changes. + * This was a GenericServlet, we make it an HttpServlet solely to provide a + * simple page to display status. * + * @since 0.9.30 moved from addressbook to SusiDNS * @author Ragnarok * */ public class Servlet extends HttpServlet { private static final long serialVersionUID = 1L; - private transient DaemonThread thread; + private transient Thread thread; //private String nonce; //private static final String PROP_NONCE = "addressbook.nonce"; /** - * Hack to allow susidns to kick the daemon when the subscription list changes. - * URL must be /addressbook/ with wakeup param set, and nonce param set from system property. + * Simple output to verify that the addressbook servlet is running. * * (non-Javadoc) * see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse) @@ -70,6 +73,7 @@ public class Servlet extends HttpServlet { /* (non-Javadoc) * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig) */ + @SuppressWarnings("unchecked") @Override public void init(ServletConfig config) { try { @@ -82,17 +86,39 @@ public class Servlet extends HttpServlet { //System.setProperty(PROP_NONCE, this.nonce); String[] args = new String[1]; args[0] = config.getInitParameter("home"); - this.thread = new DaemonThread(args); - this.thread.setDaemon(true); - this.thread.setName("Addressbook"); - this.thread.start(); - //System.out.println("INFO: Starting Addressbook " + Daemon.VERSION); - //System.out.println("INFO: config root under " + args[0]); + try { + ClassLoader cl = getServletContext().getClassLoader(); + Class cls = Class.forName("net.i2p.addressbook.DaemonThread", true, cl); + // We do it this way so that if we can't find addressbook, + // the whole thing doesn't die. + // We do add addressbook.jar in WebAppConfiguration, + // so this is just in case. + //Thread t = new DaemonThread(args); + Thread t = (Thread) cls.getConstructor(String[].class).newInstance((Object)args); + t.setDaemon(true); + t.setName("Addressbook"); + t.start(); + this.thread = t; + //System.out.println("INFO: Starting Addressbook " + Daemon.VERSION); + //System.out.println("INFO: config root under " + args[0]); + } catch (Throwable t) { + // addressbook.jar may not be in the classpath + I2PAppContext.getGlobalContext().logManager().getLog(Servlet.class).logAlways(Log.WARN, "Addressbook thread not started: " + t); + } } + @SuppressWarnings("unchecked") @Override public void destroy() { - this.thread.halt(); + if (this.thread != null) { + //((DaemonThread)this.thread).halt(); + try { + ClassLoader cl = getServletContext().getClassLoader(); + Class cls = Class.forName("net.i2p.addressbook.DaemonThread", true, cl); + Object t = cls.cast(this.thread); + cls.getDeclaredMethod("halt").invoke(t); + } catch (Throwable t) {} + } super.destroy(); } } diff --git a/apps/susidns/src/java/src/net/i2p/addressbook/servlet/package.html b/apps/susidns/src/java/src/net/i2p/addressbook/servlet/package.html new file mode 100644 index 000000000..644aab502 --- /dev/null +++ b/apps/susidns/src/java/src/net/i2p/addressbook/servlet/package.html @@ -0,0 +1,8 @@ + + +

+The servlet that starts the addressbook DaemonThread. +Moved from addressbook to SusiDNS in 0.9.30. +

+ + diff --git a/apps/susidns/src/jsp/addressbook.jsp b/apps/susidns/src/jsp/addressbook.jsp index 0ceed8e81..8e694b25d 100644 --- a/apps/susidns/src/jsp/addressbook.jsp +++ b/apps/susidns/src/jsp/addressbook.jsp @@ -136,6 +136,7 @@ ${book.loadBookMessages}