Author: toad
Date: 2007-06-12 17:32:13 +0000 (Tue, 12 Jun 2007)
New Revision: 13535
Modified:
trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties
trunk/freenet/src/freenet/node/updater/NodeUpdateManager.java
trunk/freenet/src/freenet/node/updater/RevocationChecker.java
trunk/freenet/src/freenet/node/updater/UpdateOverMandatoryManager.java
Log:
If a peer says the auto-update is blown, tell the user, and prevent
auto-updating while we try to fetch it (except we don't fetch it yet).
Modified: trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties
===================================================================
--- trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties 2007-06-12
17:09:04 UTC (rev 13534)
+++ trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties 2007-06-12
17:32:13 UTC (rev 13535)
@@ -607,6 +607,13 @@
PeerManagerUserAlert.tooOldNeverConnectedPeers=One or more of your node's
peers have never connected in the two weeks since they were added. Consider
removing them since they are marginally affecting performance (wasting packets
talking to nodes that aren't there).
PeerManagerUserAlert.tooOldNeverConnectedPeersTitle=Never connected peer(s)
too old
PeerManagerUserAlert.twoConns=This node has only two connections. Performance
and security will not be very good, and your node is not doing any routing for
other nodes. Your node is embedded like a 'chain' in the network and does not
contribute (much) to the network's health. Try to get at least 3 (ideally more
like 5-10) connected peers at any given time.
+PeersSayKeyBlownAlert.intro=One or more of your peers says that the
auto-update key is blown! This means that an attacker may know the private key
for the auto-update system and can therefore cause your node to run code of his
choice! The auto-update system has been disabled. It is also possible that your
peers are deliberately lying about it.
+PeersSayKeyBlownAlert.fetching=Your node is attempting to download the
revocation certificate to find out more details.
+PeersSayKeyBlownAlert.failedFetch=Your node has been unable to download the
revocation certificate. Possible causes include an attack on your node to try
to get you to update despite the key being blown, or your nodes lying about the
key being blown. Please contact the developers or other Freenet users to sort
out this mess.
+PeersSayKeyBlownAlert.connectedSayBlownLabel=These connected nodes say that
the key has been blown (we are trying to download the revocation cert from
them):
+PeersSayKeyBlownAlert.disconnectedSayBlownLabel=These nodes told us that the
key has been blown, but then disconnected, so we could not fetch the revocation
certificate:
+PeersSayKeyBlownAlert.failedTransferSayBlownLabel=These nodes told us that the
key has been blown, but then failed to transfer the revocation certificate:
+PeersSayKeyBlownAlert.titleWithCount=Auto-update key blown according to
${count} peer(s)!
PluginManager.cannotSetOnceLoaded=Cannot set the plugins list once loaded
PluginManager.loadedOnStartup=Plugins to load on startup
PluginManager.loadedOnStartupLong=Classpath, name and location for plugins to
load when node starts up
Modified: trunk/freenet/src/freenet/node/updater/NodeUpdateManager.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/NodeUpdateManager.java
2007-06-12 17:09:04 UTC (rev 13534)
+++ trunk/freenet/src/freenet/node/updater/NodeUpdateManager.java
2007-06-12 17:32:13 UTC (rev 13535)
@@ -64,6 +64,7 @@
final RevocationChecker revocationChecker;
private String revocationMessage;
private boolean hasBeenBlown;
+ private boolean peersSayBlown;
/** Is there a new main jar ready to deploy? */
private boolean hasNewMainJar;
@@ -329,6 +330,7 @@
synchronized(this) {
if(!(hasNewMainJar || hasNewExtJar)) return false; //
no jar
if(hasBeenBlown) return false; // Duh
+ if(peersSayBlown) return false;
// Don't immediately deploy if still fetching
startedMillisAgo = now -
Math.max(startedFetchingNextMainJar, startedFetchingNextExtJar);
if(startedMillisAgo < WAIT_FOR_SECOND_FETCH_TO_COMPLETE)
@@ -350,11 +352,18 @@
try {
synchronized(this) {
if(hasBeenBlown) {
- String msg = "Trying to update but key
has been blown! Message was "+revocationMessage;
+ String msg = "Trying to update but key
has been blown! Not updating, message was "+revocationMessage;
Logger.error(this, msg);
System.err.println(msg);
return;
}
+ if(peersSayBlown) {
+ String msg = "Trying to update but at
least one peer says the key has been blown! Not updating.";
+ Logger.error(this, msg);
+ System.err.println(msg);
+ return;
+
+ }
if(!isEnabled()) return;
if(!(isAutoUpdateAllowed || armed)) return;
if(!isReadyToDeployUpdate(false)) return;
@@ -785,4 +794,18 @@
}
+ /** Called when a peer indicates in its UOMAnnounce that it has fetched
the revocation key
+ * (or failed to do so in a way suggesting that somebody knows the key).
+ * @param source The node which is claiming this.
+ */
+ void peerClaimsKeyBlown(PeerNode source) {
+ // Note that UpdateOverMandatoryManager manages the list of
peers who think this.
+ // All we have to do is cancel the update.
+
+ synchronized(this) {
+ peersSayBlown = false;
+ armed = false;
+ }
+ }
+
}
Modified: trunk/freenet/src/freenet/node/updater/RevocationChecker.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/RevocationChecker.java
2007-06-12 17:09:04 UTC (rev 13534)
+++ trunk/freenet/src/freenet/node/updater/RevocationChecker.java
2007-06-12 17:32:13 UTC (rev 13535)
@@ -155,6 +155,10 @@
}
private void moveBlob() {
+ if(tmpBlobFile == null) {
+ Logger.error(this, "No temporary binary blob file
moving it: may not be able to propagate revocation, bug???");
+ return;
+ }
if(!tmpBlobFile.renameTo(blobFile)) {
blobFile.delete();
if(!tmpBlobFile.renameTo(blobFile)) {
@@ -162,23 +166,24 @@
System.err.println("Not able to rename binary
blob for revocation fetcher: "+tmpBlobFile+" -> "+blobFile+" - may not be able
to tell other peers about this revocation");
}
}
- if(tmpBlobFile != null)
- tmpBlobFile.renameTo(blobFile);
}
public void onFailure(FetchException e, ClientGetter state) {
Logger.minor(this, "Revocation fetch failed: "+e);
- if(tmpBlobFile != null) tmpBlobFile.delete();
logMINOR = Logger.shouldLog(Logger.MINOR, this);
int errorCode = e.getMode();
boolean completed = false;
long now = System.currentTimeMillis();
- if(errorCode == FetchException.CANCELLED) return; // cancelled
by us above, or killed; either way irrelevant and doesn't need to be restarted
+ if(errorCode == FetchException.CANCELLED) {
+ if(tmpBlobFile != null) tmpBlobFile.delete();
+ return; // cancelled by us above, or killed; either way
irrelevant and doesn't need to be restarted
+ }
if(e.isFatal()) {
manager.blow("Permanent error fetching revocation
(error inserting the revocation key?): "+e.toString());
moveBlob(); // other peers need to know
return;
}
+ if(tmpBlobFile != null) tmpBlobFile.delete();
if(e.newURI != null) {
manager.blow("Revocation URI redirecting to
"+e.newURI+" - maybe you set the revocation URI to the update URI?");
}
Modified: trunk/freenet/src/freenet/node/updater/UpdateOverMandatoryManager.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/UpdateOverMandatoryManager.java
2007-06-12 17:09:04 UTC (rev 13534)
+++ trunk/freenet/src/freenet/node/updater/UpdateOverMandatoryManager.java
2007-06-12 17:32:13 UTC (rev 13535)
@@ -3,9 +3,20 @@
* http://www.gnu.org/ for further details of the GPL. */
package freenet.node.updater;
+import java.net.MalformedURLException;
+import java.util.HashSet;
+import java.util.Vector;
+
+import freenet.io.comm.AsyncMessageCallback;
import freenet.io.comm.DMT;
import freenet.io.comm.Message;
+import freenet.io.comm.NotConnectedException;
+import freenet.keys.FreenetURI;
+import freenet.l10n.L10n;
import freenet.node.PeerNode;
+import freenet.node.useralerts.UserAlert;
+import freenet.support.HTMLNode;
+import freenet.support.Logger;
/**
* Co-ordinates update over mandatory. Update over mandatory = updating from
your peers, even
@@ -18,8 +29,19 @@
final NodeUpdateManager updateManager;
+ /** Set of WeakReferences to PeerNode's which say (or said before they
disconnected)
+ * the key has been revoked */
+ private final HashSet nodesSayKeyRevoked;
+ /** Set of WeakReferences to PeerNode's which say the key has been
revoked but failed
+ * to transfer the revocation key. */
+ private final HashSet nodesSayKeyRevokedFailedTransfer;
+
+ private UserAlert alert;
+
public UpdateOverMandatoryManager(NodeUpdateManager manager) {
this.updateManager = manager;
+ nodesSayKeyRevoked = new HashSet();
+ nodesSayKeyRevokedFailedTransfer = new HashSet();
}
/**
@@ -29,7 +51,7 @@
* @param source The PeerNode which sent the message.
* @return True unless we don't want the message (in this case, always
true).
*/
- public boolean handleAnnounce(Message m, PeerNode source) {
+ public boolean handleAnnounce(Message m, final PeerNode source) {
String jarKey = m.getString(DMT.MAIN_JAR_KEY);
String extraJarKey = m.getString(DMT.EXTRA_JAR_KEY);
String revocationKey = m.getString(DMT.REVOCATION_KEY);
@@ -43,14 +65,263 @@
long extraJarFileLength = m.getLong(DMT.EXTRA_JAR_FILE_LENGTH);
int pingTime = m.getInt(DMT.PING_TIME);
int delayTime = m.getInt(DMT.BWLIMIT_DELAY_TIME);
- System.err.println("Update Over Mandatory offer from node
"+source.getPeer()+" : "+source.getName()+":");
- System.err.println("Main jar key: "+jarKey+"
version="+mainJarVersion+" length="+mainJarFileLength);
- System.err.println("Extra jar key: "+extraJarKey+"
version="+extraJarVersion+" length="+extraJarFileLength);
- System.err.println("Revocation key: "+revocationKey+"
found="+haveRevocationKey+" length="+revocationKeyFileLength+" last had 3 DNFs
"+revocationKeyLastTried+" ms ago, "+revocationKeyDNFs+" DNFs so far");
- System.err.println("Load stats: "+pingTime+"ms ping,
"+delayTime+"ms bwlimit delay time");
+
+ // Log it
+
+ boolean logMINOR = Logger.shouldLog(Logger.MINOR, this);
+ if(logMINOR) {
+ Logger.minor(this, "Update Over Mandatory offer from
node "+source.getPeer()+" : "+source.getName()+":");
+ Logger.minor(this, "Main jar key: "+jarKey+"
version="+mainJarVersion+" length="+mainJarFileLength);
+ Logger.minor(this, "Extra jar key: "+extraJarKey+"
version="+extraJarVersion+" length="+extraJarFileLength);
+ Logger.minor(this, "Revocation key: "+revocationKey+"
found="+haveRevocationKey+" length="+revocationKeyFileLength+" last had 3 DNFs
"+revocationKeyLastTried+" ms ago, "+revocationKeyDNFs+" DNFs so far");
+ Logger.minor(this, "Load stats: "+pingTime+"ms ping,
"+delayTime+"ms bwlimit delay time");
+ }
+
+ // Now the core logic
+
+ // First off, if a node says it has the revocation key, and its
key is the same as ours,
+ // we should 1) suspend any auto-updates and tell the user, 2)
try to download it, and
+ // 3) if the download fails, move the notification; if the
download succeeds, process it
+
+ if(haveRevocationKey) {
+ // First, is the key the same as ours?
+ try {
+ FreenetURI revocationURI = new
FreenetURI(revocationKey);
+
if(/*revocationURI.equals(updateManager.revocationURI)*/true) {
+
+ // Uh oh...
+
+ // Have to do this first to avoid race
condition
+ synchronized(this) {
+ nodesSayKeyRevoked.add(source);
+ }
+
+ // Disable the update
+
updateManager.peerClaimsKeyBlown(source);
+
+ // Tell the user
+ alertUser();
+
+ System.err.println("Your peer
"+source.getPeer()+" : "+source.getName()+" says that the auto-update key is
blown!");
+ System.err.println("Attempting to fetch
it...");
+
+ // Try to transfer it.
+
+ Message msg =
DMT.createUOMRequestRevocation();
+ source.sendAsync(msg, new
AsyncMessageCallback() {
+ public void acknowledged() {
+ // Ok
+ }
+ public void disconnected() {
+ // :(
+
System.err.println("Failed to send request for revocation key to
"+source.getPeer()+" : "+source.getName()+" because it disconnected!");
+
synchronized(UpdateOverMandatoryManager.this) {
+
nodesSayKeyRevokedFailedTransfer.add(source);
+ }
+ }
+ public void fatalError() {
+ // Not good!
+
System.err.println("Failed to send request for revocation key to
"+source.getPeer()+" : "+source.getName()+" because of a fatal error.");
+ }
+ public void sent() {
+ // Cool
+ }
+ }, 0, null);
+
+ // The reply message will start the
transfer. It includes the revocation URI
+ // so we can tell if anything wierd is
happening.
+
+ } else {
+ // Should probably also be a useralert?
+ Logger.normal(this, "Node "+source+"
sent us a UOM claiming that the auto-update key was blown, but it used a
different key to us: \nour key="+updateManager.revocationURI+"\nhis
key="+revocationURI);
+ System.err.println("Node
"+source.getPeer()+" : "+source.getName()+" sent us a UOM claiming that the
revocation key was blown, but it used a different key to us: \nour
key="+updateManager.revocationURI+"\nhis key="+revocationURI);
+ }
+ } catch (MalformedURLException e) {
+ // Should maybe be a useralert?
+ Logger.error(this, "Node "+source+" sent us a
UOMAnnounce claiming that the auto-update key was blown, but it had an invalid
revocation URI: "+revocationKey+" : "+e, e);
+ System.err.println("Node "+source.getPeer()+" :
"+source.getName()+" sent us a UOMAnnounce claiming that the revocation key was
blown, but it had an invalid revocation URI: "+revocationKey+" : "+e);
+ } catch (NotConnectedException e) {
+ System.err.println("Node "+source+" says that
the auto-update key was blown, but has now gone offline! Something BAD is
happening!");
+ Logger.error(this, "Node "+source+" says that
the auto-update key was blown, but has now gone offline! Something BAD is
happening!");
+ synchronized(UpdateOverMandatoryManager.this) {
+
nodesSayKeyRevokedFailedTransfer.add(source);
+ }
+ }
+
+ }
+
return true;
}
+ private void alertUser() {
+ synchronized(this) {
+ if(alert != null) return;
+ alert = new PeersSayKeyBlownAlert();
+ }
+ updateManager.node.clientCore.alerts.register(alert);
+ }
+
+ class PeersSayKeyBlownAlert implements UserAlert {
+
+ public String dismissButtonText() {
+ // Cannot dismiss
+ return null;
+ }
+
+ public HTMLNode getHTMLText() {
+ HTMLNode div = new HTMLNode("div");
+
+ div.addChild("p").addChild("#", l10n("intro"));
+
+ PeerNode[][] nodes = getNodesSayBlown();
+ PeerNode[] nodesSayBlownConnected = nodes[0];
+ PeerNode[] nodesSayBlownDisconnected = nodes[1];
+ PeerNode[] nodesSayBlownFailedTransfer = nodes[2];
+
+ if(nodesSayBlownConnected.length > 0) {
+ div.addChild("p").addChild("#",
l10n("fetching"));
+ } else {
+ div.addChild("p").addChild("#",
l10n("failedFetch"));
+ }
+
+ if(nodesSayBlownConnected.length > 0) {
+ div.addChild("p").addChild("#",
l10n("connectedSayBlownLabel"));
+ HTMLNode list = div.addChild("ul");
+ for(int
i=0;i<nodesSayBlownConnected.length;i++) {
+ list.addChild("li",
nodesSayBlownConnected[i].getName()+"
("+nodesSayBlownConnected[i].getPeer()+")");
+ }
+ }
+
+ if(nodesSayBlownDisconnected.length > 0) {
+ div.addChild("p").addChild("#",
l10n("disconnectedSayBlownLabel"));
+ HTMLNode list = div.addChild("ul");
+ for(int
i=0;i<nodesSayBlownDisconnected.length;i++) {
+ list.addChild("li",
nodesSayBlownDisconnected[i].getName()+"
("+nodesSayBlownDisconnected[i].getPeer()+")");
+ }
+ }
+
+ if(nodesSayBlownFailedTransfer.length > 0) {
+ div.addChild("p").addChild("#",
l10n("failedTransferSayBlownLabel"));
+ HTMLNode list = div.addChild("ul");
+ for(int
i=0;i<nodesSayBlownFailedTransfer.length;i++) {
+ list.addChild("li",
nodesSayBlownFailedTransfer[i].getName()+"
("+nodesSayBlownFailedTransfer[i].getPeer()+")");
+ }
+ }
+
+ return div;
+ }
+
+ private String l10n(String key) {
+ return L10n.getString("PeersSayKeyBlownAlert."+key);
+ }
+
+ private String l10n(String key, String pattern, String value) {
+ return L10n.getString("PeersSayKeyBlownAlert."+key,
pattern, value);
+ }
+
+ public short getPriorityClass() {
+ return UserAlert.CRITICAL_ERROR;
+ }
+
+ public String getText() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(l10n("intro")).append("\n\n");
+ PeerNode[][] nodes = getNodesSayBlown();
+ PeerNode[] nodesSayBlownConnected = nodes[0];
+ PeerNode[] nodesSayBlownDisconnected = nodes[1];
+ PeerNode[] nodesSayBlownFailedTransfer = nodes[2];
+
+ if(nodesSayBlownConnected.length > 0) {
+ sb.append(l10n("fetching")).append("\n\n");
+ } else {
+ sb.append(l10n("failedFetch")).append("\n\n");
+ }
+
+ if(nodesSayBlownConnected.length > 0) {
+
sb.append(l10n("connectedSayBlownLabel")).append("\n\n");
+ for(int
i=0;i<nodesSayBlownConnected.length;i++) {
+
sb.append(nodesSayBlownConnected[i].getName()+"
("+nodesSayBlownConnected[i].getPeer()+")").append("\n");
+ }
+ sb.append("\n");
+ }
+
+ if(nodesSayBlownDisconnected.length > 0) {
+ sb.append(l10n("disconnectedSayBlownLabel"));
+
+ for(int
i=0;i<nodesSayBlownDisconnected.length;i++) {
+
sb.append(nodesSayBlownDisconnected[i].getName()+"
("+nodesSayBlownDisconnected[i].getPeer()+")").append("\n");
+ }
+ sb.append("\n");
+ }
+
+ if(nodesSayBlownFailedTransfer.length > 0) {
+ sb.append(l10n("failedTransferSayBlownLabel"));
+
+ for(int
i=0;i<nodesSayBlownFailedTransfer.length;i++) {
+
sb.append(nodesSayBlownFailedTransfer[i].getName()+"
("+nodesSayBlownFailedTransfer[i].getPeer()+")").append('\n');
+ }
+ sb.append("\n");
+ }
+
+ return sb.toString();
+ }
+
+ public String getTitle() {
+ return l10n("titleWithCount", "count",
Integer.toString(nodesSayKeyRevoked.size()));
+ }
+
+ public boolean isValid() {
+ return true;
+ }
+
+ public void isValid(boolean validity) {
+ // Do nothing
+ }
+
+ public void onDismiss() {
+ // Do nothing
+ }
+
+ public boolean shouldUnregisterOnDismiss() {
+ // Can't dismiss
+ return false;
+ }
+
+ public boolean userCanDismiss() {
+ // Can't dismiss
+ return false;
+ }
+
+ }
+
+ public PeerNode[][] getNodesSayBlown() {
+ Vector nodesConnectedSayRevoked = new Vector();
+ Vector nodesDisconnectedSayRevoked = new Vector();
+ Vector nodesFailedSayRevoked = new Vector();
+ synchronized(this) {
+ PeerNode[] nodesSayRevoked = (PeerNode[])
nodesSayKeyRevoked.toArray(new PeerNode[nodesSayKeyRevoked.size()]);
+ for(int i=0;i<nodesSayRevoked.length;i++) {
+ PeerNode pn = nodesSayRevoked[i];
+
if(nodesSayKeyRevokedFailedTransfer.contains(pn))
+ nodesFailedSayRevoked.add(pn);
+ else
+ nodesConnectedSayRevoked.add(pn);
+ }
+ }
+ for(int i=0;i<nodesConnectedSayRevoked.size();i++) {
+ PeerNode pn = (PeerNode)
nodesConnectedSayRevoked.get(i);
+ if(!pn.isConnected()) {
+ nodesDisconnectedSayRevoked.add(pn);
+ nodesConnectedSayRevoked.remove(i);
+ i--;
+ continue;
+ }
+ }
+ return new PeerNode[][] {
+ (PeerNode[])
nodesConnectedSayRevoked.toArray(new PeerNode[nodesConnectedSayRevoked.size()]),
+ (PeerNode[])
nodesDisconnectedSayRevoked.toArray(new
PeerNode[nodesDisconnectedSayRevoked.size()]),
+ (PeerNode[]) nodesFailedSayRevoked.toArray(new
PeerNode[nodesFailedSayRevoked.size()]),
+ };
+ }
-
}