Author: nextgens
Date: 2007-04-12 01:53:25 +0000 (Thu, 12 Apr 2007)
New Revision: 12589

Added:
   trunk/freenet/src/freenet/node/fcp/TestDDAReply.java
   trunk/freenet/src/freenet/node/fcp/TestDDARequest.java
   trunk/freenet/src/freenet/node/fcp/TestDDAResponse.java
   trunk/freenet/src/freenet/node/fcp/testDDAComplete.java
Modified:
   trunk/freenet/src/freenet/node/fcp/FCPConnectionHandler.java
   trunk/freenet/src/freenet/node/fcp/FCPMessage.java
   trunk/freenet/src/freenet/support/io/FileUtil.java
Log:
Implement testDDA (#1086)
        I will document it soon ... it's not yet enforced nor doing anything 
really useful. But FCP client authors might start to play with it ... and 
People can review the code :)

Modified: trunk/freenet/src/freenet/node/fcp/FCPConnectionHandler.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/FCPConnectionHandler.java        
2007-04-11 23:13:26 UTC (rev 12588)
+++ trunk/freenet/src/freenet/node/fcp/FCPConnectionHandler.java        
2007-04-12 01:53:25 UTC (rev 12589)
@@ -1,14 +1,51 @@
 package freenet.node.fcp;

+import java.io.File;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.net.Socket;
 import java.util.HashMap;
+import java.util.Random;

+import freenet.support.HexUtil;
 import freenet.support.Logger;
 import freenet.support.api.BucketFactory;
+import freenet.support.io.FileUtil;

 public class FCPConnectionHandler {
+       static final private class DirectoryAccess {
+               final boolean canWrite;
+               final boolean canRead;
+               
+               public DirectoryAccess(boolean canRead, boolean canWrite) {
+                       this.canRead = canRead;
+                       this.canWrite = canWrite;
+               }
+       }
+       
+       public class DDACheckJob {
+               final File directory, readFilename, writeFilename;
+               final String readContent, writeContent; 
+               
+               /**
+                * null if not requested.
+                */
+               DDACheckJob(File directory, File readFilename, File 
writeFilename) {
+                       this.directory = directory;
+                       this.readFilename = readFilename;
+                       this.writeFilename = writeFilename;
+                       
+                       Random r = new Random();
+                       byte[] random = new byte[512];
+                       
+                       r.nextBytes(random);
+                       this.readContent = new 
String(HexUtil.bytesToHex(random));

+                       r.nextBytes(random);
+                       this.writeContent = new 
String(HexUtil.bytesToHex(random));
+               }
+       }
+
        final FCPServer server;
        final Socket sock;
        final FCPConnectionInputHandler inputHandler;
@@ -20,6 +57,9 @@
        private FCPClient client;
        final BucketFactory bf;
        final HashMap requestsByIdentifier;
+       // We are confident that the given client can access those
+       private final HashMap checkedDirectories = new HashMap();
+       private final HashMap inTestDirectories = new HashMap();

        public FCPConnectionHandler(Socket s, FCPServer server) {
                this.sock = s;
@@ -244,5 +284,108 @@
        public boolean hasFullAccess() {
                return 
server.allowedHostsFullAccess.allowed(sock.getInetAddress());
        }
+       
+       protected boolean allowDownloadTo(File filename) {
+               String parentDirectory = 
FileUtil.getCanonicalFile(filename).getPath();
+               DirectoryAccess da = null;
+               
+               synchronized (checkedDirectories) {
+                               da = (DirectoryAccess) 
checkedDirectories.get(parentDirectory);
+               }
+               
+               if(da == null)
+                       return false;
+               else
+                       return da.canWrite;
+       }

+       protected boolean allowUploadFrom(File filename) {
+               String parentDirectory = 
FileUtil.getCanonicalFile(filename).getPath();
+               DirectoryAccess da = null;
+               
+               synchronized (checkedDirectories) {
+                               da = (DirectoryAccess) 
checkedDirectories.get(parentDirectory);
+               }
+               
+               if(da == null)
+                       return false;
+               else
+                       return da.canRead;
+       }
+       
+       /**
+        * SHOULD BE CALLED ONLY FROM TestDDAComplete!
+        * @param path
+        * @param read
+        * @param write
+        */
+       protected void registerTestDDAResult(String path, boolean read, boolean 
write) {
+               DirectoryAccess da = new DirectoryAccess(read, write);
+               
+               synchronized (checkedDirectories) {
+                               checkedDirectories.put(path, da);
+               }
+       }
+       
+       /**
+        * Return a DDACheckJob : the one we created and have enqueued
+        * @param path
+        * @param read : is Read access requested ?
+        * @param write : is Write access requested ?
+        * @return
+        * @throws IllegalArgumentException
+        * 
+        * FIXME: Maybe we need to enqueue a PS job to delete the created file 
after something like ... 5 mins ?
+        */
+       protected DDACheckJob enqueueDDACheck(String path, boolean read, 
boolean write) throws IllegalArgumentException {
+               File directory = FileUtil.getCanonicalFile(new File(path));
+               if(!directory.isDirectory())
+                       throw new IllegalArgumentException("The specified path 
isn't a directory!");
+               
+               File writeFile = (write ? new File(path, "DDACheck-" + new 
Random().nextInt() + ".tmp") : null);
+               
+               File readFile = null;
+               if(read) {
+                       try {
+                               readFile = File.createTempFile("DDACheck-", 
".tmp", directory);
+                               readFile.deleteOnExit();
+                       } catch (IOException e) {
+                               // Now we know it: we can't write there ;)
+                               readFile = null;
+                       }
+               }
+               
+               DDACheckJob result = new DDACheckJob(directory, readFile, 
writeFile);
+               synchronized (inTestDirectories) {
+                       inTestDirectories.put(directory, result);
+               }
+               
+               if(read){
+                       try {
+                               FileWriter fw = new 
FileWriter(result.readFilename);
+                               fw.write(result.readContent);
+                               fw.close();
+                       } catch (IOException e) {
+                               Logger.error(this, "Got a IOE while creating 
the file (" + readFile.toString() + " ! " + e.getMessage());
+                       }
+               }
+               
+               return result;
+       }
+       
+       /**
+        * Return a DDACheckJob or null if not found
+        * @param path
+        * @return the DDACheckJob
+        * @throws IllegalArgumentException
+        */
+       protected DDACheckJob popDDACheck(String path) throws 
IllegalArgumentException {
+               File directory = FileUtil.getCanonicalFile(new File(path));
+               if(!directory.isDirectory())
+                       throw new IllegalArgumentException("The specified path 
isn't a directory!");
+               
+               synchronized (inTestDirectories) {
+                       return (DDACheckJob)inTestDirectories.get(directory);
+               }
+       }
 }

Modified: trunk/freenet/src/freenet/node/fcp/FCPMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/FCPMessage.java  2007-04-11 23:13:26 UTC 
(rev 12588)
+++ trunk/freenet/src/freenet/node/fcp/FCPMessage.java  2007-04-12 01:53:25 UTC 
(rev 12589)
@@ -77,9 +77,10 @@
                        return new ShutdownMessage();
                if(name.equals(SubscribeUSKMessage.name))
                        return new SubscribeUSKMessage(fs);
-               // Removed until security issues sorted out
-//             if(name.equals(TestDDAMessage.name))
-//                     return new TestDDAMessage(fs);
+               if(name.equals(TestDDARequest.NAME))
+                       return new TestDDARequest(fs);
+               if(name.equals(TestDDAResponse.NAME))
+                       return new TestDDAResponse(fs);
                if(name.equals(WatchGlobal.name))
                        return new WatchGlobal(fs);
                if(name.equals("Void"))
@@ -87,9 +88,6 @@
                if(name.equals(NodeHelloMessage.name))
                        return new NodeHelloMessage(fs);
                throw new 
MessageInvalidException(ProtocolErrorMessage.INVALID_MESSAGE, "Unknown message 
name "+name, null, false);
-//             if(name.equals("ClientPut"))
-//                     return new ClientPutFCPMessage(fs);
-               // TODO Auto-generated method stub
        }

        /**

Added: trunk/freenet/src/freenet/node/fcp/TestDDAReply.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/TestDDAReply.java                        
        (rev 0)
+++ trunk/freenet/src/freenet/node/fcp/TestDDAReply.java        2007-04-12 
01:53:25 UTC (rev 12589)
@@ -0,0 +1,54 @@
+/* This code is part of Freenet. It is distributed under the GNU General
+ * Public License, version 2 (or at your option any later version). See
+ * http://www.gnu.org/ for further details of the GPL. */
+package freenet.node.fcp;
+
+import freenet.node.Node;
+import freenet.node.fcp.FCPConnectionHandler.DDACheckJob;
+import freenet.support.SimpleFieldSet;
+
+/**
+ * client -> node: DDARequest { WantRead=true, WantWrite=true, Dir=/tmp/blah }
+ * node -> client: DDAReply { Dir=/tmp/blah, ReadFilename=random1, 
WriteFilename=random2, ContentToWrite=random3 }
+ * client -> node: DDAResponse { Dir=/tmp/blah, ReadContent=blah }
+ * node -> client: DDAComplete { Dir=/tmp/blah, ReadAllowed=true, 
WriteAllowed=true }
+ * 
+ * @author Florent Daignière <nextgens at freenetproject.org>
+ *
+ */
+public class TestDDAReply extends FCPMessage {
+       public static final String NAME = "TestDDAReply";
+       public static final String READ_FILENAME = "ReadFilename";
+       public static final String WRITE_FILENAME = "WriteFilename";
+       public static final String CONTENT_TO_WRITE = "ContentToWrite";
+       
+       final DDACheckJob checkJob;
+       
+       TestDDAReply(DDACheckJob job) {
+               this.checkJob = job;
+       }
+       
+       public SimpleFieldSet getFieldSet() {
+               SimpleFieldSet sfs = new SimpleFieldSet(true);
+               sfs.putSingle(TestDDARequest.DIRECTORY, 
checkJob.directory.toString());
+               
+               if(checkJob.readFilename != null) {
+                       sfs.putSingle(READ_FILENAME, 
checkJob.readFilename.toString());
+               }
+               
+               if(checkJob.writeFilename != null) {
+                       sfs.putSingle(WRITE_FILENAME, 
checkJob.writeFilename.toString());
+                       sfs.putSingle(CONTENT_TO_WRITE, checkJob.writeContent);
+               }
+               
+               return sfs;
+       }
+
+       public String getName() {
+               return NAME;
+       }
+
+       public void run(FCPConnectionHandler handler, Node node) throws 
MessageInvalidException {
+               throw new 
MessageInvalidException(ProtocolErrorMessage.INVALID_MESSAGE, NAME + " goes 
from server to client not the other way around", NAME, false);
+       }
+}

Copied: trunk/freenet/src/freenet/node/fcp/TestDDARequest.java (from rev 12576, 
trunk/freenet/src/freenet/node/fcp/TestDDAMessage.java)
===================================================================
--- trunk/freenet/src/freenet/node/fcp/TestDDARequest.java                      
        (rev 0)
+++ trunk/freenet/src/freenet/node/fcp/TestDDARequest.java      2007-04-12 
01:53:25 UTC (rev 12589)
@@ -0,0 +1,62 @@
+/* This code is part of Freenet. It is distributed under the GNU General
+ * Public License, version 2 (or at your option any later version). See
+ * http://www.gnu.org/ for further details of the GPL. */
+package freenet.node.fcp;
+
+import freenet.node.Node;
+import freenet.node.fcp.FCPConnectionHandler.DDACheckJob;
+import freenet.support.SimpleFieldSet;
+
+/**
+ * client -> node: DDARequest { WantRead=true, WantWrite=true, Dir=/tmp/blah }
+ * node -> client: DDAReply { Dir=/tmp/blah, ReadFilename=random1, 
WriteFilename=random2, ContentToWrite=random3 }
+ * client -> node: DDAResponse { Dir=/tmp/blah, ReadContent=blah }
+ * node -> client: DDAComplete { Dir=/tmp/blah, ReadAllowed=true, 
WriteAllowed=true }
+ * 
+ *  @author Florent Daignière <nextgens at freenetproject.org>
+ */
+public class TestDDARequest extends FCPMessage {
+       public static final String NAME = "TestDDARequest";
+       public static final String DIRECTORY = "Directory";
+       public static final String WANT_READ = "WantRead";
+       public static final String WANT_WRITE = "WantWrite";
+       
+       final String identifier;
+       final boolean wantRead, wantWrite;
+       
+       
+       /** 
+        * @throws MessageInvalidException 
+        */
+       public TestDDARequest(SimpleFieldSet fs) throws MessageInvalidException 
{
+               identifier = fs.get(DIRECTORY);
+               if(identifier == null)
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "No Directory 
given!", null, false);
+               if(identifier.length() == 0)
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "The specified 
Directory can't be empty!", null, false);
+               
+               wantRead = fs.getBoolean(WANT_READ, false);
+               wantWrite = fs.getBoolean(WANT_WRITE, false);
+               if((wantRead == false) && (wantWrite == false))
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.INVALID_MESSAGE, "Both "+ 
WANT_READ + " and " + WANT_WRITE + " are set to false: what's the point of 
sending a message?", identifier, false);
+       }
+
+       public SimpleFieldSet getFieldSet() {
+               return null;
+       }
+
+       public String getName() {
+               return NAME;
+       }
+
+       public void run(FCPConnectionHandler handler, Node node) throws 
MessageInvalidException {
+               DDACheckJob job;
+               try {
+                       job = handler.enqueueDDACheck(identifier, wantRead, 
wantWrite);
+               } catch (IllegalArgumentException e) {
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, e.getMessage(), 
identifier, false);
+               }
+               TestDDAReply reply = new TestDDAReply(job);
+               handler.outputHandler.queue(reply);
+       }
+}

Added: trunk/freenet/src/freenet/node/fcp/TestDDAResponse.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/TestDDAResponse.java                     
        (rev 0)
+++ trunk/freenet/src/freenet/node/fcp/TestDDAResponse.java     2007-04-12 
01:53:25 UTC (rev 12589)
@@ -0,0 +1,59 @@
+/* This code is part of Freenet. It is distributed under the GNU General
+ * Public License, version 2 (or at your option any later version). See
+ * http://www.gnu.org/ for further details of the GPL. */
+package freenet.node.fcp;
+
+import freenet.node.Node;
+import freenet.node.fcp.FCPConnectionHandler.DDACheckJob;
+import freenet.support.SimpleFieldSet;
+
+/**
+ * client -> node: DDARequest { WantRead=true, WantWrite=true, Dir=/tmp/blah }
+ * node -> client: DDAReply { Dir=/tmp/blah, ReadFilename=random1, 
WriteFilename=random2, ContentToWrite=random3 }
+ * client -> node: DDAResponse { Dir=/tmp/blah, ReadContent=blah }
+ * node -> client: DDAComplete { Dir=/tmp/blah, ReadAllowed=true, 
WriteAllowed=true }
+ * 
+ * @author Florent Daignière <nextgens at freenetproject.org>
+ *
+ */
+public class TestDDAResponse extends FCPMessage {
+       public static final String NAME = "TestDDAResponse";
+       public static final String READ_CONTENT = "ReadContent";
+       
+       final String identifier;
+       final String readContent;
+       
+       public TestDDAResponse(SimpleFieldSet sfs) throws 
MessageInvalidException {
+               identifier = sfs.get(TestDDARequest.DIRECTORY);
+               if(identifier == null)
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "No Directory 
given!", null, false);
+               if(identifier.length() == 0)
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "The specified 
Directory can't be empty!", null, false);
+               
+               readContent = sfs.get(READ_CONTENT);
+       }
+
+       public SimpleFieldSet getFieldSet() {
+               return null;
+       }
+
+       public String getName() {
+               return NAME;
+       }
+
+       public void run(FCPConnectionHandler handler, Node node) throws 
MessageInvalidException {
+               DDACheckJob job;
+               try {
+                        job = handler.popDDACheck(identifier);
+               } catch (IllegalArgumentException e) {
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, e.getMessage(), 
identifier, false);
+               }
+               if(job == null)
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.INVALID_MESSAGE, "The node doesn't 
know that testDDA identifier! double check it! (" + identifier + ").", 
identifier, false);
+               else if((job.readFilename != null) && (readContent == null))
+                       throw new 
MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "You need to send " 
+ READ_CONTENT + " back to the node if you specify " + TestDDARequest.WANT_READ 
+ " in " + TestDDARequest.NAME + '.', identifier, false);
+               
+               testDDAComplete reply = new testDDAComplete(handler, job, 
readContent);
+               handler.outputHandler.queue(reply);
+       }
+}

Added: trunk/freenet/src/freenet/node/fcp/testDDAComplete.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/testDDAComplete.java                     
        (rev 0)
+++ trunk/freenet/src/freenet/node/fcp/testDDAComplete.java     2007-04-12 
01:53:25 UTC (rev 12589)
@@ -0,0 +1,88 @@
+/* This code is part of Freenet. It is distributed under the GNU General
+ * Public License, version 2 (or at your option any later version). See
+ * http://www.gnu.org/ for further details of the GPL. */
+package freenet.node.fcp;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+import freenet.node.Node;
+import freenet.node.fcp.FCPConnectionHandler.DDACheckJob;
+import freenet.support.Logger;
+import freenet.support.SimpleFieldSet;
+
+/**
+ * client -> node: DDARequest { WantRead=true, WantWrite=true, Dir=/tmp/blah }
+ * node -> client: DDAReply { Dir=/tmp/blah, ReadFilename=random1, 
WriteFilename=random2, ContentToWrite=random3 }
+ * client -> node: DDAResponse { Dir=/tmp/blah, ReadContent=blah }
+ * node -> client: DDAComplete { Dir=/tmp/blah, ReadAllowed=true, 
WriteAllowed=true }
+ * 
+ * @author Florent Daignière <nextgens at freenetproject.org>
+ *
+ */
+public class testDDAComplete extends FCPMessage {
+       public static String NAME = "TestDDAComplete";
+       public static String READ_ALLOWED = "ReadAllowed";
+       public static String WRITE_ALLOWED = "WriteAllowed";
+
+       final DDACheckJob checkJob;
+       final String readContentFromClient;
+       private final FCPConnectionHandler handler;
+       
+       public testDDAComplete(FCPConnectionHandler handler, DDACheckJob job, 
String readContent) {
+               this.checkJob = job;
+               this.readContentFromClient = readContent;
+               this.handler = handler;
+       }
+
+       public SimpleFieldSet getFieldSet() {
+               SimpleFieldSet sfs = new SimpleFieldSet(true);
+               
+               sfs.putSingle(TestDDARequest.DIRECTORY, 
checkJob.directory.toString());
+               
+               boolean isReadAllowed = false; 
+               boolean isWriteAllowed = false;
+               
+               if(checkJob.readFilename != null) {
+                       isReadAllowed = (readContentFromClient != null) &&  
(checkJob.readContent.equals(readContentFromClient));
+                       // cleanup in any case : we created it!... let's hope 
the client will do the same on its side.
+                       checkJob.readFilename.delete();
+                       sfs.putSingle(READ_ALLOWED, 
String.valueOf(isReadAllowed));
+               }
+               
+               if(checkJob.writeFilename != null) {
+                       File maybeWrittenFile = checkJob.writeFilename;
+                       if (maybeWrittenFile.exists() && 
maybeWrittenFile.isFile() && maybeWrittenFile.canRead()) {
+                               try {
+                                       FileReader fr = new 
FileReader(maybeWrittenFile);
+                                       StringBuffer sb = new StringBuffer();
+                                       
+                                       int current = fr.read();                
                        
+                                       while(current != -1) {
+                                               sb.append((char)current);
+                                               current = fr.read();
+                                       }
+                                       
+                                       fr.close();
+                                       isWriteAllowed = 
checkJob.writeContent.equals(sb.toString().trim());
+                               } catch (IOException e) {
+                                       Logger.error(this, "Caught an IOE 
trying to read the file (" + maybeWrittenFile + ")! " + e.getMessage());
+                               }
+                       }
+                       sfs.putSingle(WRITE_ALLOWED, 
String.valueOf(isWriteAllowed));
+               }
+               
+               handler.registerTestDDAResult(checkJob.directory.toString(), 
isReadAllowed, isWriteAllowed);
+               
+               return sfs;
+       }
+
+       public String getName() {
+               return NAME;
+       }
+
+       public void run(FCPConnectionHandler handler, Node node) throws 
MessageInvalidException {
+               throw new 
MessageInvalidException(ProtocolErrorMessage.INVALID_MESSAGE, NAME + " goes 
from server to client not the other way around", NAME, false);
+       }
+}

Modified: trunk/freenet/src/freenet/support/io/FileUtil.java
===================================================================
--- trunk/freenet/src/freenet/support/io/FileUtil.java  2007-04-11 23:13:26 UTC 
(rev 12588)
+++ trunk/freenet/src/freenet/support/io/FileUtil.java  2007-04-12 01:53:25 UTC 
(rev 12589)
@@ -33,22 +33,14 @@
                return blockUsage + filenameUsage + extra;
        }

-       /** Is possParent a parent of filename?
-        * FIXME Move somewhere generic. 
-        * Why doesn't java provide this? :( */
+       /**
+        *  Is possParent a parent of filename?
+        * Why doesn't java provide this? :(
+        * */
        public static boolean isParent(File possParent, File filename) {
-               File canonParent;
-               File canonFile;
-               try {
-                       canonParent = possParent.getCanonicalFile();
-               } catch (IOException e) {
-                       canonParent = possParent.getAbsoluteFile();
-               }
-               try {
-                       canonFile = filename.getCanonicalFile();
-               } catch (IOException e) {
-                       canonFile = filename.getAbsoluteFile();
-               }
+               File canonParent = FileUtil.getCanonicalFile(possParent);
+               File canonFile = FileUtil.getCanonicalFile(filename);
+
                if(isParentInner(possParent, filename)) return true;
                if(isParentInner(possParent, canonFile)) return true;
                if(isParentInner(canonParent, filename)) return true;
@@ -63,4 +55,14 @@
                        if(filename == null) return false;
                }
        }
+       
+       public static File getCanonicalFile(File file){
+               File parentDirectory;
+               try {
+                       parentDirectory = file.getCanonicalFile();
+               } catch (IOException e) {
+                       parentDirectory = file.getAbsoluteFile();
+               }
+               return parentDirectory;
+       }
 }


Reply via email to