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.openssh_compat import primes
from twisted.cred import portal
from twisted.internet.error import ProcessDone, ProcessTerminated
from twisted.internet import defer
from twisted.internet.interfaces import IFinishableConsumer
@ -786,6 +787,7 @@ class GeneralSFTPFile(PrefixingLogMixin):
self.async.addCallbacks(_resize, eventually_errback(d))
return d
class StoppableList:
def __init__(self, items):
self.items = items
@ -796,17 +798,63 @@ class StoppableList:
pass
class SFTPHandler(PrefixingLogMixin):
implements(ISFTPServer)
def __init__(self, user):
PrefixingLogMixin.__init__(self, facility="tahoe.sftp")
if noisy: self.log(".__init__(%r)" % (user,), level=NOISY)
class Reason:
def __init__(self, value):
self.value = value
self.check_abort = user.check_abort
self.client = user.client
self.root = user.root
self.username = user.username
self.convergence = user.convergence
class SFTPUserHandler(ConchUser, PrefixingLogMixin):
implements(ISFTPServer, ISession)
def __init__(self, check_abort, client, rootnode, username):
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):
self.log(".gotVersion(%r, %r)" % (otherVersion, extData), level=OPERATIONAL)
@ -1262,14 +1310,9 @@ class Dispatcher:
def requestAvatar(self, avatarID, mind, interface):
assert interface == IConchUser
rootnode = self.client.create_node_from_uri(avatarID.rootcap)
convergence = self.client.convergence
logged_out = {'flag': False}
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)
handler = SFTPUserHandler(self.client, rootnode, avatarID.username)
return (interface, handler, handler.logout)
class SFTPServer(service.MultiService):
def __init__(self, client, accountfile, accounturl,
@ -1287,7 +1330,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 NeedRootcapLookupScheme("must provide some translation")
raise NeedRootcapLookupScheme("must provide an account file or URL")
pubkey = keys.Key.fromFile(pubkey_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.internet import defer
from twisted.python.failure import Failure
from twisted.internet.error import ProcessDone, ProcessTerminated
sftp = None
sftpd = None
@ -89,18 +90,15 @@ class Handler(GridTestMixin, ShouldFailMixin, unittest.TestCase):
self.basedir = "sftp/" + basedir
self.set_up_grid(num_clients=num_clients, num_servers=num_servers)
def check_abort():
pass
self.check_abort = lambda: False
self.client = self.g.clients[0]
self.username = "alice"
self.convergence = "convergence"
d = self.client.create_dirnode()
def _created_root(node):
self.root = node
self.root_uri = node.get_uri()
self.user = sftpd.SFTPUser(check_abort, self.client, self.root, self.username, self.convergence)
self.handler = sftpd.SFTPHandler(self.user)
self.handler = sftpd.SFTPUserHandler(self.check_abort, self.client, self.root, self.username)
d.addCallback(_created_root)
return d
@ -915,3 +913,31 @@ class Handler(GridTestMixin, ShouldFailMixin, unittest.TestCase):
self.shouldFailWithSFTPError(sftp.FX_PERMISSION_DENIED, "makeDirectory small",
self.handler.makeDirectory, "small", {}))
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