= Tahoe FTP Frontend =

All Tahoe client nodes can run a frontend FTP server, allowing regular FTP
clients to access the virtual filesystem.

Since Tahoe does not use user accounts or passwords, the FTP server must be
configured with a way to translate USER+PASS into a root directory cap. Two
mechanisms are provided. The first is a simple flat file with one account per
line. The second is an HTTP-based login mechanism, backed by simple PHP
script and a database. The latter form is used by allmydata.com to provide
secure access to customer rootcaps.

== Configuring an Account File ==

To configure the first form, create a file (probably in
BASEDIR/private/ftp.accounts) in which each non-comment/non-blank line is a
space-separated line of (USERNAME, PASSWORD, ROOTCAP), like so:

 % cat BASEDIR/private/ftp.accounts
 # This is a password file, (username, password, rootcap)
 alice password URI:DIR2:ioej8xmzrwilg772gzj4fhdg7a:wtiizszzz2rgmczv4wl6bqvbv33ag4kvbr6prz3u6w3geixa6m6a
 bob sekrit URI:DIR2:6bdmeitystckbl9yqlw7g56f4e:serp5ioqxnh34mlbmzwvkp3odehsyrr7eytt5f64we3k9hhcrcja

Then add the following lines to the BASEDIR/tahoe.cfg file:

 [ftpd]
 enabled = true
 ftp.port = 8021
 ftp.accounts.file = private/ftp.accounts

The FTP server will listen on the given port number. The ftp.accounts.file
pathname will be interpreted relative to the node's BASEDIR.

== Configuring an Account Server ==

Determine the URL of the account server, say https://example.com/login . Then
add the following lines to BASEDIR/tahoe.cfg:

 [ftpd]
 enabled = true
 ftp.port = 8021
 ftp.accounts.url = https://example.com/login

== Dependencies ==

The FTP server requires Twisted's "vfs" component, which is not included in
the Twisted-8.1.0 release. If there is no newer release available, it may be
necessary to run Twisted from the SVN trunk to obtain this component.

In addition, the following patch must be applied (as of r24943) to enable
asynchronous closing of file-upload operations:

Index: twisted/protocols/ftp.py
===================================================================
--- twisted/protocols/ftp.py	(revision 24956)
+++ twisted/protocols/ftp.py	(working copy)
@@ -1049,7 +1049,6 @@
                 cons = ASCIIConsumerWrapper(cons)
 
             d = self.dtpInstance.registerConsumer(cons)
-            d.addCallbacks(cbSent, ebSent)
 
             # Tell them what to doooo
             if self.dtpInstance.isConnected:
@@ -1062,6 +1061,8 @@
         def cbOpened(file):
             d = file.receive()
             d.addCallback(cbConsumer)
+            d.addCallback(lambda ignored: file.close())
+            d.addCallbacks(cbSent, ebSent)
             return d
 
         def ebOpened(err):
@@ -1434,7 +1435,14 @@
         @rtype: C{Deferred} of C{IConsumer}
         """
 
+    def close():
+        """
+        Perform any post-write work that needs to be done. This method may
+        only be invoked once on each provider, and will always be invoked
+        after receive().
 
+        @rtype: C{Deferred} of anything: the value is ignored
+        """
 
 def _getgroups(uid):
     """Return the primary and supplementary groups for the given UID.
@@ -1795,6 +1803,8 @@
         # FileConsumer will close the file object
         return defer.succeed(FileConsumer(self.fObj))
 
+    def close(self):
+        return defer.succeed(None)
 
 
 class FTPRealm:
Index: twisted/vfs/adapters/ftp.py
===================================================================
--- twisted/vfs/adapters/ftp.py	(revision 24956)
+++ twisted/vfs/adapters/ftp.py	(working copy)
@@ -295,6 +295,11 @@
         """
         return defer.succeed(IConsumer(self.node))
 
+    def close(self):
+        """
+        Perform post-write actions.
+        """
+        return defer.succeed(None)
 
 
 class _FileToConsumerAdapter(object):
