From d95b01a214b0acb1e0c3caaacd188ed4847a06df Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@lothar.com>
Date: Sun, 22 Feb 2009 17:24:26 -0700
Subject: [PATCH] ftpd/sftpd: stop using RuntimeError, for #639

---
 src/allmydata/frontends/auth.py  |  5 +++++
 src/allmydata/frontends/ftpd.py  |  4 ++--
 src/allmydata/frontends/sftpd.py | 11 +++++++----
 3 files changed, 14 insertions(+), 6 deletions(-)

diff --git a/src/allmydata/frontends/auth.py b/src/allmydata/frontends/auth.py
index 6adbebb2..82ef1c65 100644
--- a/src/allmydata/frontends/auth.py
+++ b/src/allmydata/frontends/auth.py
@@ -5,6 +5,11 @@ from twisted.internet import defer
 from twisted.cred import error, checkers, credentials
 from allmydata.util import base32
 
+class NeedRootcapLookupScheme(Exception):
+    """Accountname+Password-based access schemes require some kind of
+    mechanism to translate name+passwd pairs into a rootcap, either a file of
+    name/passwd/rootcap tuples, or a server to do the translation."""
+
 class FTPAvatarID:
     def __init__(self, username, rootcap):
         self.username = username
diff --git a/src/allmydata/frontends/ftpd.py b/src/allmydata/frontends/ftpd.py
index 598c8db0..c187d86a 100644
--- a/src/allmydata/frontends/ftpd.py
+++ b/src/allmydata/frontends/ftpd.py
@@ -267,7 +267,7 @@ class Handler:
         d.addCallback(_got_parent)
         return d
 
-from auth import AccountURLChecker, AccountFileChecker
+from auth import AccountURLChecker, AccountFileChecker, NeedRootcapLookupScheme
 
 
 class Dispatcher:
@@ -303,7 +303,7 @@ class FTPServer(service.MultiService):
             p.registerChecker(c)
         if not accountfile and not accounturl:
             # we could leave this anonymous, with just the /uri/CAP form
-            raise RuntimeError("must provide some translation")
+            raise NeedRootcapLookupScheme("must provide some translation")
 
         f = ftp.FTPFactory(p)
         s = strports.service(ftp_portstr, f)
diff --git a/src/allmydata/frontends/sftpd.py b/src/allmydata/frontends/sftpd.py
index d29aa8a6..aa15bbba 100644
--- a/src/allmydata/frontends/sftpd.py
+++ b/src/allmydata/frontends/sftpd.py
@@ -116,6 +116,9 @@ class StoppableList:
 class FakeStat:
     pass
 
+class BadRemoveRequest(Exception):
+    pass
+
 class SFTPHandler:
     implements(ISFTPServer)
     def __init__(self, user):
@@ -259,9 +262,9 @@ class SFTPHandler:
             d = parent.get(childname)
             def _got_child(child):
                 if must_be_directory and not IDirectoryNode.providedBy(child):
-                    raise RuntimeError("rmdir called on a file")
+                    raise BadRemoveRequest("rmdir called on a file")
                 if must_be_file and IDirectoryNode.providedBy(child):
-                    raise RuntimeError("rmfile called on a directory")
+                    raise BadRemoveRequest("rmfile called on a directory")
                 return parent.delete(childname)
             d.addCallback(_got_child)
             d.addErrback(self._convert_error)
@@ -421,7 +424,7 @@ class SFTPHandler:
 # then you get SFTPHandler(user)
 components.registerAdapter(SFTPHandler, SFTPUser, ISFTPServer)
 
-from auth import AccountURLChecker, AccountFileChecker
+from auth import AccountURLChecker, AccountFileChecker, NeedRootcapLookupScheme
 
 class Dispatcher:
     implements(portal.IRealm)
@@ -452,7 +455,7 @@ class SFTPServer(service.MultiService):
             p.registerChecker(c)
         if not accountfile and not accounturl:
             # we could leave this anonymous, with just the /uri/CAP form
-            raise RuntimeError("must provide some translation")
+            raise NeedRootcapLookupScheme("must provide some translation")
 
         pubkey = keys.Key.fromFile(pubkey_file)
         privkey = keys.Key.fromFile(privkey_file)
-- 
2.45.2