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
self.async.addCallbacks(_resize, eventually_errback(d))
return d
+
class StoppableList:
def __init__(self, items):
self.items = items
pass
-class SFTPHandler(PrefixingLogMixin):
- implements(ISFTPServer)
- def __init__(self, user):
+class Reason:
+ def __init__(self, value):
+ self.value = value
+
+
+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)" % (user,), level=NOISY)
+ 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)
- self.check_abort = user.check_abort
- self.client = user.client
- self.root = user.root
- self.username = user.username
- self.convergence = user.convergence
+ 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)
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,
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)
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
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
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
\ No newline at end of file