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()]),
+               };
+       }

-       
 }


Reply via email to