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