diff --git a/history.txt b/history.txt index b78ed20897..cf0023e135 100644 --- a/history.txt +++ b/history.txt @@ -1,7 +1,20 @@ +2012-03-20 zzz + * i2psnark: Message area tweaks and clear link + * NetDB: + - Don't reread RI if netdb date is recent + - Prevent LS/RI overwrites + - Disallow hash mismatches in RI files + - Reseed won't fetch our own RI + - Reseed won't overwrite recent RIs + * Router: Make runRouter() public + 2012-03-19 sponge * Plugins: Less confusing message, fix CNFE by catch and ignore on delete. Order and reverse order plugin names for start/stop all cases. +2012-03-18 zzz + * Jetty: Fix check alias override + 2012-03-17 zzz * BuildHandler: Implement restart and shutdown to stop the thread * Jetty: Don't extract wars diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 80cfb9c849..5b1ba79e39 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 18; + public final static long BUILD = 19; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java index 58a1621ffd..d49e0a9abd 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -684,7 +684,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { // if it hasn't changed, no need to do anything return rv; } - } catch (ClassCastException cce) {} + } catch (ClassCastException cce) { + throw new IllegalArgumentException("Attempt to replace RI with " + leaseSet); + } String err = validate(key, leaseSet); if (err != null) @@ -811,7 +813,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { return store(key, routerInfo, true); } - public RouterInfo store(Hash key, RouterInfo routerInfo, boolean persist) throws IllegalArgumentException { + RouterInfo store(Hash key, RouterInfo routerInfo, boolean persist) throws IllegalArgumentException { if (!_initialized) return null; RouterInfo rv = null; @@ -821,7 +823,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { // no need to validate return rv; } - } catch (ClassCastException cce) {} + } catch (ClassCastException cce) { + throw new IllegalArgumentException("Attempt to replace LS with " + routerInfo); + } String err = validate(key, routerInfo); if (err != null) diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java index 414f5a798e..6353d7a595 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java @@ -392,12 +392,17 @@ class PersistentDataStore extends TransientDataStore { private class ReadRouterJob extends JobImpl { private final File _routerFile; private final Hash _key; + private long _knownDate; + /** + * @param key must match the RI hash in the file + */ public ReadRouterJob(File routerFile, Hash key) { super(PersistentDataStore.this._context); _routerFile = routerFile; _key = key; } + public String getName() { return "Read RouterInfo"; } private boolean shouldRead() { @@ -405,19 +410,21 @@ class PersistentDataStore extends TransientDataStore { DatabaseEntry data = get(_key, false); if (data == null) return true; if (data.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) { - long knownDate = ((RouterInfo)data).getPublished(); + _knownDate = ((RouterInfo)data).getPublished(); long fileDate = _routerFile.lastModified(); - if (fileDate > knownDate) - return true; - else - return false; + // don't overwrite recent netdb RIs with reseed data + return fileDate > _knownDate + (60*60*1000); } else { - // wtf - return true; + // wtf - prevent injection from reseeding + _log.error("Prevented LS overwrite by RI " + _key + " from " + _routerFile); + return false; } } + public void runJob() { if (!shouldRead()) return; + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Reading " + _routerFile); try { InputStream fis = null; boolean corrupt = false; @@ -432,6 +439,15 @@ class PersistentDataStore extends TransientDataStore { _log.error("The router " + ri.getIdentity().calculateHash().toBase64() + " is from a different network"); + } else if (!ri.getIdentity().calculateHash().equals(_key)) { + // prevent injection from reseeding + // this is checked in KNDF.validate() but catch it sooner and log as error. + corrupt = true; + _log.error(ri.getIdentity().calculateHash() + " does not match " + _key + " from " + _routerFile); + } else if (ri.getPublished() <= _knownDate) { + // Don't store but don't delete + if (_log.shouldLog(Log.WARN)) + _log.warn("Skipping since netdb newer than " + _routerFile); } else { try { // persist = false so we don't write what we just read @@ -441,7 +457,7 @@ class PersistentDataStore extends TransientDataStore { // so add it here. getContext().profileManager().heardAbout(ri.getIdentity().getHash(), ri.getPublished()); } catch (IllegalArgumentException iae) { - _log.info("Refused locally loaded routerInfo - deleting"); + _log.info("Refused locally loaded routerInfo - deleting", iae); corrupt = true; } } diff --git a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java index 342e29bb6c..0d896bc46f 100644 --- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java +++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java @@ -334,6 +334,8 @@ public class Reseeder { // This isn't really URLs, but Base64 hashes // but they may include % encoding Set urls = new HashSet(1024); + Hash ourHash = _context.routerHash(); + String ourB64 = ourHash != null ? ourHash.toBase64() : null; int cur = 0; int total = 0; while (total++ < 1000) { @@ -352,7 +354,13 @@ public class Reseeder { continue; } String name = content.substring(start + ("href=\"" + ROUTERINFO_PREFIX).length(), end); - urls.add(name); + // never load our own RI + if (ourB64 == null || !name.contains(ourB64)) { + urls.add(name); + } else { + if (_log.shouldLog(Log.INFO)) + _log.info("Skipping our own RI"); + } cur = end + 1; } if (total <= 0) { @@ -372,7 +380,8 @@ public class Reseeder { System.setProperty(PROP_STATUS, _("Reseeding: fetching router info from seed URL ({0} successful, {1} errors).", fetched, errors)); - fetchSeed(seedURL, iter.next()); + if (!fetchSeed(seedURL, iter.next())) + continue; fetched++; if (echoStatus) { System.out.print("."); @@ -408,8 +417,9 @@ public class Reseeder { * We do NOT validate the received data here - that is done in PersistentDataStore * * @param peer The Base64 hash, may include % encoding. It is decoded and validated here. + * @return true on success, false if skipped */ - private void fetchSeed(String seedURL, String peer) throws IOException, URISyntaxException { + private boolean fetchSeed(String seedURL, String peer) throws IOException, URISyntaxException { // Use URI to do % decoding of the B64 hash (some servers escape ~ and =) // Also do basic hash validation. This prevents stuff like // .. or / in the file name @@ -420,13 +430,16 @@ public class Reseeder { byte[] hash = Base64.decode(b64); if (hash == null || hash.length != Hash.HASH_LENGTH) throw new IOException("bad hash " + peer); + Hash ourHash = _context.routerHash(); + if (ourHash != null && DataHelper.eq(hash, ourHash.getData())) + return false; URL url = new URL(seedURL + (seedURL.endsWith("/") ? "" : "/") + ROUTERINFO_PREFIX + peer + ROUTERINFO_SUFFIX); byte data[] = readURL(url); if (data == null || data.length <= 0) throw new IOException("Failed fetch of " + url); - writeSeed(b64, data); + return writeSeed(b64, data); } /** @return null on error */ @@ -468,16 +481,24 @@ public class Reseeder { /** * @param name valid Base64 hash + * @return true on success, false if skipped */ - private void writeSeed(String name, byte data[]) throws IOException { + private boolean writeSeed(String name, byte data[]) throws IOException { String dirName = "netDb"; // _context.getProperty("router.networkDatabase.dbDir", "netDb"); File netDbDir = new SecureDirectory(_context.getRouterDir(), dirName); if (!netDbDir.exists()) { - boolean ok = netDbDir.mkdirs(); + netDbDir.mkdirs(); + } + File file = new File(netDbDir, ROUTERINFO_PREFIX + name + ROUTERINFO_SUFFIX); + // don't overwrite recent file + // TODO: even better would be to compare to last-mod date from eepget + if (file.exists() && file.lastModified() > _context.clock().now() - 60*60*1000) { + if (_log.shouldLog(Log.INFO)) + _log.info("Skipping RI, ours is recent: " + file); + return false; } FileOutputStream fos = null; try { - File file = new File(netDbDir, ROUTERINFO_PREFIX + name + ROUTERINFO_SUFFIX); fos = new SecureFileOutputStream(file); fos.write(data); if (_log.shouldLog(Log.INFO)) @@ -487,6 +508,7 @@ public class Reseeder { if (fos != null) fos.close(); } catch (IOException ioe) {} } + return true; } }