[freenet-cvs] r12589 - in trunk/freenet/src/freenet: node/fcp support/io

nextgens at freenetproject.org nextgens at freenetproject.org
Thu Apr 12 01:53:26 UTC 2007


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;
+	}
 }




More information about the cvs mailing list