SFTP: implement execCommand to interoperate with clients that issue a 'df -P -k /' command. Also eliminate use of Zope adaptation.

This commit is contained in:
david-sarah 2010-05-15 18:27:54 -07:00
parent 6ba6c97a7a
commit 8028bf01bc
2 changed files with 93 additions and 24 deletions

View File

@ -16,6 +16,7 @@ from twisted.conch.interfaces import ISFTPServer, ISFTPFile, IConchUser, ISessio
from twisted.conch.avatar import ConchUser from twisted.conch.avatar import ConchUser
from twisted.conch.openssh_compat import primes from twisted.conch.openssh_compat import primes
from twisted.cred import portal from twisted.cred import portal
from twisted.internet.error import ProcessDone, ProcessTerminated
from twisted.internet import defer from twisted.internet import defer
from twisted.internet.interfaces import IFinishableConsumer from twisted.internet.interfaces import IFinishableConsumer
@ -786,6 +787,7 @@ class GeneralSFTPFile(PrefixingLogMixin):
self.async.addCallbacks(_resize, eventually_errback(d)) self.async.addCallbacks(_resize, eventually_errback(d))
return d return d
class StoppableList: class StoppableList:
def __init__(self, items): def __init__(self, items):
self.items = items self.items = items
@ -796,17 +798,63 @@ class StoppableList:
pass pass
class SFTPHandler(PrefixingLogMixin): class Reason:
implements(ISFTPServer) def __init__(self, value):
def __init__(self, user): self.value = value
PrefixingLogMixin.__init__(self, facility="tahoe.sftp")
if noisy: self.log(".__init__(%r)" % (user,), level=NOISY)
self.check_abort = user.check_abort
self.client = user.client class SFTPUserHandler(ConchUser, PrefixingLogMixin):
self.root = user.root implements(ISFTPServer, ISession)
self.username = user.username def __init__(self, check_abort, client, rootnode, username):
self.convergence = user.convergence ConchUser.__init__(self)
PrefixingLogMixin.__init__(self, facility="tahoe.sftp")
if noisy: self.log(".__init__(%r, %r, %r, %r)" %
(check_abort, client, rootnode, username), level=NOISY)
self.channelLookup["session"] = session.SSHSession
self.subsystemLookup["sftp"] = FileTransferServer
self.client = client
self.root = rootnode
self.username = username
self.convergence = client.convergence
self.logged_out = False
def logout(self):
self.logged_out = True
def check_abort(self):
return self.logged_out
# ISession
# This is needed because some clients may try to issue a 'df' command.
def getPty(self, terminal, windowSize, attrs):
self.log(".getPty(%r, %r, %r)" % (terminal, windowSize, attrs), level=OPERATIONAL)
def openShell(self, protocol):
self.log(".openShell(%r)" % (protocol,), level=OPERATIONAL)
raise NotImplementedError
def execCommand(self, protocol, cmd):
self.log(".execCommand(%r, %r)" % (protocol, cmd), level=OPERATIONAL)
if cmd == "df -P -k /":
protocol.write("Filesystem 1024-blocks Used Available Capacity Mounted on\n"
"tahoe 628318530 314159265 314159265 50% /\n")
protocol.processEnded(Reason(ProcessDone(None)))
else:
protocol.processEnded(Reason(ProcessTerminated(exitCode=1)))
def windowChanged(self, newWindowSize):
self.log(".windowChanged(%r)" % (newWindowSize,), level=OPERATIONAL)
def eofReceived(self):
self.log(".eofReceived()", level=OPERATIONAL)
def closed(self):
self.log(".closed()", level=OPERATIONAL)
# ISFTPServer
def gotVersion(self, otherVersion, extData): def gotVersion(self, otherVersion, extData):
self.log(".gotVersion(%r, %r)" % (otherVersion, extData), level=OPERATIONAL) self.log(".gotVersion(%r, %r)" % (otherVersion, extData), level=OPERATIONAL)
@ -1262,14 +1310,9 @@ class Dispatcher:
def requestAvatar(self, avatarID, mind, interface): def requestAvatar(self, avatarID, mind, interface):
assert interface == IConchUser assert interface == IConchUser
rootnode = self.client.create_node_from_uri(avatarID.rootcap) rootnode = self.client.create_node_from_uri(avatarID.rootcap)
convergence = self.client.convergence handler = SFTPUserHandler(self.client, rootnode, avatarID.username)
logged_out = {'flag': False} return (interface, handler, handler.logout)
def check_abort():
return logged_out['flag']
def logout():
logged_out['flag'] = True
s = SFTPUser(check_abort, self.client, rootnode, avatarID.username, convergence)
return (interface, s, logout)
class SFTPServer(service.MultiService): class SFTPServer(service.MultiService):
def __init__(self, client, accountfile, accounturl, def __init__(self, client, accountfile, accounturl,
@ -1287,7 +1330,7 @@ class SFTPServer(service.MultiService):
p.registerChecker(c) p.registerChecker(c)
if not accountfile and not accounturl: if not accountfile and not accounturl:
# we could leave this anonymous, with just the /uri/CAP form # we could leave this anonymous, with just the /uri/CAP form
raise NeedRootcapLookupScheme("must provide some translation") raise NeedRootcapLookupScheme("must provide an account file or URL")
pubkey = keys.Key.fromFile(pubkey_file) pubkey = keys.Key.fromFile(pubkey_file)
privkey = keys.Key.fromFile(privkey_file) privkey = keys.Key.fromFile(privkey_file)

View File

@ -5,6 +5,7 @@ from stat import S_IFREG, S_IFDIR
from twisted.trial import unittest from twisted.trial import unittest
from twisted.internet import defer from twisted.internet import defer
from twisted.python.failure import Failure from twisted.python.failure import Failure
from twisted.internet.error import ProcessDone, ProcessTerminated
sftp = None sftp = None
sftpd = None sftpd = None
@ -89,18 +90,15 @@ class Handler(GridTestMixin, ShouldFailMixin, unittest.TestCase):
self.basedir = "sftp/" + basedir self.basedir = "sftp/" + basedir
self.set_up_grid(num_clients=num_clients, num_servers=num_servers) self.set_up_grid(num_clients=num_clients, num_servers=num_servers)
def check_abort(): self.check_abort = lambda: False
pass
self.client = self.g.clients[0] self.client = self.g.clients[0]
self.username = "alice" self.username = "alice"
self.convergence = "convergence"
d = self.client.create_dirnode() d = self.client.create_dirnode()
def _created_root(node): def _created_root(node):
self.root = node self.root = node
self.root_uri = node.get_uri() self.root_uri = node.get_uri()
self.user = sftpd.SFTPUser(check_abort, self.client, self.root, self.username, self.convergence) self.handler = sftpd.SFTPUserHandler(self.check_abort, self.client, self.root, self.username)
self.handler = sftpd.SFTPHandler(self.user)
d.addCallback(_created_root) d.addCallback(_created_root)
return d return d
@ -915,3 +913,31 @@ class Handler(GridTestMixin, ShouldFailMixin, unittest.TestCase):
self.shouldFailWithSFTPError(sftp.FX_PERMISSION_DENIED, "makeDirectory small", self.shouldFailWithSFTPError(sftp.FX_PERMISSION_DENIED, "makeDirectory small",
self.handler.makeDirectory, "small", {})) self.handler.makeDirectory, "small", {}))
return d return d
def test_execCommand(self):
class FakeProtocol:
def __init__(self):
self.output = ""
self.reason = None
def write(self, data):
self.output += data
def processEnded(self, reason):
self.reason = reason
protocol_ok = FakeProtocol()
protocol_error = FakeProtocol()
d = self._set_up("execCommand")
d.addCallback(lambda ign: self.handler.execCommand(protocol_ok, "df -P -k /"))
d.addCallback(lambda ign: self.failUnlessIn("1024-blocks", protocol_ok.output))
d.addCallback(lambda ign: self.failUnless(isinstance(protocol_ok.reason.value, ProcessDone)))
d.addCallback(lambda ign: self.handler.eofReceived())
d.addCallback(lambda ign: self.handler.closed())
d.addCallback(lambda ign: self.handler.execCommand(protocol_error, "error"))
d.addCallback(lambda ign: self.failUnlessEqual(protocol_error.output, ""))
d.addCallback(lambda ign: self.failUnless(isinstance(protocol_error.reason.value, ProcessTerminated)))
d.addCallback(lambda ign: self.failUnlessEqual(protocol_error.reason.value.exitCode, 1))
return d