- 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
This commit is contained in:
zzz
2012-03-20 18:38:28 +00:00
parent 3da6ccfbbe
commit e3da181cea
5 changed files with 74 additions and 19 deletions

View File

@@ -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 2012-03-19 sponge
* Plugins: Less confusing message, fix CNFE by catch and ignore on delete. * Plugins: Less confusing message, fix CNFE by catch and ignore on delete.
Order and reverse order plugin names for start/stop all cases. Order and reverse order plugin names for start/stop all cases.
2012-03-18 zzz
* Jetty: Fix check alias override
2012-03-17 zzz 2012-03-17 zzz
* BuildHandler: Implement restart and shutdown to stop the thread * BuildHandler: Implement restart and shutdown to stop the thread
* Jetty: Don't extract wars * Jetty: Don't extract wars

View File

@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */ /** deprecated */
public final static String ID = "Monotone"; public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION; public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 18; public final static long BUILD = 19;
/** for example "-test" */ /** for example "-test" */
public final static String EXTRA = ""; public final static String EXTRA = "";

View File

@@ -684,7 +684,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
// if it hasn't changed, no need to do anything // if it hasn't changed, no need to do anything
return rv; return rv;
} }
} catch (ClassCastException cce) {} } catch (ClassCastException cce) {
throw new IllegalArgumentException("Attempt to replace RI with " + leaseSet);
}
String err = validate(key, leaseSet); String err = validate(key, leaseSet);
if (err != null) if (err != null)
@@ -811,7 +813,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
return store(key, routerInfo, true); 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; if (!_initialized) return null;
RouterInfo rv = null; RouterInfo rv = null;
@@ -821,7 +823,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
// no need to validate // no need to validate
return rv; return rv;
} }
} catch (ClassCastException cce) {} } catch (ClassCastException cce) {
throw new IllegalArgumentException("Attempt to replace LS with " + routerInfo);
}
String err = validate(key, routerInfo); String err = validate(key, routerInfo);
if (err != null) if (err != null)

View File

@@ -392,12 +392,17 @@ class PersistentDataStore extends TransientDataStore {
private class ReadRouterJob extends JobImpl { private class ReadRouterJob extends JobImpl {
private final File _routerFile; private final File _routerFile;
private final Hash _key; private final Hash _key;
private long _knownDate;
/**
* @param key must match the RI hash in the file
*/
public ReadRouterJob(File routerFile, Hash key) { public ReadRouterJob(File routerFile, Hash key) {
super(PersistentDataStore.this._context); super(PersistentDataStore.this._context);
_routerFile = routerFile; _routerFile = routerFile;
_key = key; _key = key;
} }
public String getName() { return "Read RouterInfo"; } public String getName() { return "Read RouterInfo"; }
private boolean shouldRead() { private boolean shouldRead() {
@@ -405,19 +410,21 @@ class PersistentDataStore extends TransientDataStore {
DatabaseEntry data = get(_key, false); DatabaseEntry data = get(_key, false);
if (data == null) return true; if (data == null) return true;
if (data.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) { if (data.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
long knownDate = ((RouterInfo)data).getPublished(); _knownDate = ((RouterInfo)data).getPublished();
long fileDate = _routerFile.lastModified(); long fileDate = _routerFile.lastModified();
if (fileDate > knownDate) // don't overwrite recent netdb RIs with reseed data
return true; return fileDate > _knownDate + (60*60*1000);
else
return false;
} else { } else {
// wtf // wtf - prevent injection from reseeding
return true; _log.error("Prevented LS overwrite by RI " + _key + " from " + _routerFile);
return false;
} }
} }
public void runJob() { public void runJob() {
if (!shouldRead()) return; if (!shouldRead()) return;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Reading " + _routerFile);
try { try {
InputStream fis = null; InputStream fis = null;
boolean corrupt = false; boolean corrupt = false;
@@ -432,6 +439,15 @@ class PersistentDataStore extends TransientDataStore {
_log.error("The router " _log.error("The router "
+ ri.getIdentity().calculateHash().toBase64() + ri.getIdentity().calculateHash().toBase64()
+ " is from a different network"); + " 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 { } else {
try { try {
// persist = false so we don't write what we just read // persist = false so we don't write what we just read
@@ -441,7 +457,7 @@ class PersistentDataStore extends TransientDataStore {
// so add it here. // so add it here.
getContext().profileManager().heardAbout(ri.getIdentity().getHash(), ri.getPublished()); getContext().profileManager().heardAbout(ri.getIdentity().getHash(), ri.getPublished());
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
_log.info("Refused locally loaded routerInfo - deleting"); _log.info("Refused locally loaded routerInfo - deleting", iae);
corrupt = true; corrupt = true;
} }
} }

View File

@@ -334,6 +334,8 @@ public class Reseeder {
// This isn't really URLs, but Base64 hashes // This isn't really URLs, but Base64 hashes
// but they may include % encoding // but they may include % encoding
Set<String> urls = new HashSet(1024); Set<String> urls = new HashSet(1024);
Hash ourHash = _context.routerHash();
String ourB64 = ourHash != null ? ourHash.toBase64() : null;
int cur = 0; int cur = 0;
int total = 0; int total = 0;
while (total++ < 1000) { while (total++ < 1000) {
@@ -352,7 +354,13 @@ public class Reseeder {
continue; continue;
} }
String name = content.substring(start + ("href=\"" + ROUTERINFO_PREFIX).length(), end); 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; cur = end + 1;
} }
if (total <= 0) { if (total <= 0) {
@@ -372,7 +380,8 @@ public class Reseeder {
System.setProperty(PROP_STATUS, System.setProperty(PROP_STATUS,
_("Reseeding: fetching router info from seed URL ({0} successful, {1} errors).", fetched, errors)); _("Reseeding: fetching router info from seed URL ({0} successful, {1} errors).", fetched, errors));
fetchSeed(seedURL, iter.next()); if (!fetchSeed(seedURL, iter.next()))
continue;
fetched++; fetched++;
if (echoStatus) { if (echoStatus) {
System.out.print("."); System.out.print(".");
@@ -408,8 +417,9 @@ public class Reseeder {
* We do NOT validate the received data here - that is done in PersistentDataStore * 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. * @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 =) // Use URI to do % decoding of the B64 hash (some servers escape ~ and =)
// Also do basic hash validation. This prevents stuff like // Also do basic hash validation. This prevents stuff like
// .. or / in the file name // .. or / in the file name
@@ -420,13 +430,16 @@ public class Reseeder {
byte[] hash = Base64.decode(b64); byte[] hash = Base64.decode(b64);
if (hash == null || hash.length != Hash.HASH_LENGTH) if (hash == null || hash.length != Hash.HASH_LENGTH)
throw new IOException("bad hash " + peer); 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); URL url = new URL(seedURL + (seedURL.endsWith("/") ? "" : "/") + ROUTERINFO_PREFIX + peer + ROUTERINFO_SUFFIX);
byte data[] = readURL(url); byte data[] = readURL(url);
if (data == null || data.length <= 0) if (data == null || data.length <= 0)
throw new IOException("Failed fetch of " + url); throw new IOException("Failed fetch of " + url);
writeSeed(b64, data); return writeSeed(b64, data);
} }
/** @return null on error */ /** @return null on error */
@@ -468,16 +481,24 @@ public class Reseeder {
/** /**
* @param name valid Base64 hash * @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"); String dirName = "netDb"; // _context.getProperty("router.networkDatabase.dbDir", "netDb");
File netDbDir = new SecureDirectory(_context.getRouterDir(), dirName); File netDbDir = new SecureDirectory(_context.getRouterDir(), dirName);
if (!netDbDir.exists()) { 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; FileOutputStream fos = null;
try { try {
File file = new File(netDbDir, ROUTERINFO_PREFIX + name + ROUTERINFO_SUFFIX);
fos = new SecureFileOutputStream(file); fos = new SecureFileOutputStream(file);
fos.write(data); fos.write(data);
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
@@ -487,6 +508,7 @@ public class Reseeder {
if (fos != null) fos.close(); if (fos != null) fos.close();
} catch (IOException ioe) {} } catch (IOException ioe) {}
} }
return true;
} }
} }