SFTP: work around a probable bug in twisted.conch.ssh.session:loseConnection(). Also some minor error handling cleanups.

This commit is contained in:
david-sarah 2010-05-16 18:26:06 -07:00
parent 6a2b0229f0
commit 5f9c10901b
2 changed files with 72 additions and 70 deletions

View File

@ -118,8 +118,8 @@ def _raise_error(err):
if err.check(defer.FirstError):
_raise_error(err.value.subFailure)
# We assume that the type of error is not anonymity-sensitive.
raise SFTPError(FX_FAILURE, str(err.type))
# We assume that the error message is not anonymity-sensitive.
raise SFTPError(FX_FAILURE, str(err.value))
def _repr_flags(flags):
return "|".join([f for f in
@ -512,7 +512,7 @@ SIZE_THRESHOLD = 1000
def _make_sftp_file(check_abort, flags, convergence, parent=None, childname=None, filenode=None, metadata=None):
if noisy: logmsg("_make_sftp_file(%r, %r, %r, parent=%r, childname=%r, filenode=%r, metadata=%r" %
(check_abort, flags, convergence, parent, childname, filenode, metadata), NOISY)
(check_abort, flags, convergence, parent, childname, filenode, metadata), level=NOISY)
if not (flags & (FXF_WRITE | FXF_CREAT)) and (flags & FXF_READ) and filenode and \
not filenode.is_mutable() and filenode.get_size() <= SIZE_THRESHOLD:
@ -802,7 +802,7 @@ class Reason:
class SFTPUserHandler(ConchUser, PrefixingLogMixin):
implements(ISFTPServer, ISession)
implements(ISFTPServer)
def __init__(self, client, rootnode, username):
ConchUser.__init__(self)
PrefixingLogMixin.__init__(self, facility="tahoe.sftp")
@ -811,48 +811,17 @@ class SFTPUserHandler(ConchUser, PrefixingLogMixin):
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
self._client = client
self._root = rootnode
self._username = username
self._convergence = client.convergence
self._logged_out = False
def logout(self):
self.logged_out = True
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)
protocol.write("This server supports only SFTP, not shell sessions.\n")
protocol.processEnded(Reason(ProcessTerminated(exitCode=1)))
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
return self._logged_out
def gotVersion(self, otherVersion, extData):
self.log(".gotVersion(%r, %r)" % (otherVersion, extData), level=OPERATIONAL)
@ -929,7 +898,7 @@ class SFTPUserHandler(ConchUser, PrefixingLogMixin):
raise SFTPError(FX_PERMISSION_DENIED,
"cannot create a file exclusively when it already exists")
return _make_sftp_file(self.check_abort, flags, self.convergence, filenode=root)
return _make_sftp_file(self.check_abort, flags, self._convergence, filenode=root)
else:
# case 2
childname = path[-1]
@ -984,7 +953,7 @@ class SFTPUserHandler(ConchUser, PrefixingLogMixin):
raise SFTPError(FX_PERMISSION_DENIED,
"cannot open a file for writing when the parent directory is read-only")
return _make_sftp_file(self.check_abort, flags, self.convergence, parent=parent,
return _make_sftp_file(self.check_abort, flags, self._convergence, parent=parent,
childname=childname, filenode=filenode, metadata=metadata)
def _no_child(f):
if noisy: self.log("_no_child(%r)" % (f,), level=NOISY)
@ -999,7 +968,7 @@ class SFTPUserHandler(ConchUser, PrefixingLogMixin):
raise SFTPError(FX_PERMISSION_DENIED,
"cannot create a file when the parent directory is read-only")
return _make_sftp_file(self.check_abort, flags, self.convergence, parent=parent,
return _make_sftp_file(self.check_abort, flags, self._convergence, parent=parent,
childname=childname)
d2.addCallbacks(_got_child, _no_child)
return d2
@ -1168,7 +1137,7 @@ class SFTPUserHandler(ConchUser, PrefixingLogMixin):
if extendedName == 'statvfs@openssh.com' or extendedName == 'fstatvfs@openssh.com':
# <http://dev.libssh.org/ticket/11>
return struct.pack('>QQQQQQQQQQQ',
return struct.pack('>11Q',
1024, # uint64 f_bsize /* file system block size */
1024, # uint64 f_frsize /* fundamental fs block size */
628318530, # uint64 f_blocks /* number of blocks (unit f_frsize) */
@ -1233,10 +1202,10 @@ class SFTPUserHandler(ConchUser, PrefixingLogMixin):
def _get_root(self, path):
# return (root, remaining_path)
if path and path[0] == u"uri":
d = defer.maybeDeferred(self.client.create_node_from_uri, path[1].encode('utf-8'))
d = defer.maybeDeferred(self._client.create_node_from_uri, path[1].encode('utf-8'))
d.addCallback(lambda root: (root, path[2:]))
else:
d = defer.succeed((self.root, path))
d = defer.succeed((self._root, path))
return d
def _get_parent(self, path):
@ -1316,12 +1285,12 @@ from auth import AccountURLChecker, AccountFileChecker, NeedRootcapLookupScheme
class Dispatcher:
implements(portal.IRealm)
def __init__(self, client):
self.client = client
self._client = client
def requestAvatar(self, avatarID, mind, interface):
assert interface == IConchUser
rootnode = self.client.create_node_from_uri(avatarID.rootcap)
handler = SFTPUserHandler(self.client, rootnode, avatarID.username)
rootnode = self._client.create_node_from_uri(avatarID.rootcap)
handler = SFTPUserHandler(self._client, rootnode, avatarID.username)
return (interface, handler, handler.logout)

View File

@ -7,6 +7,7 @@ from twisted.internet import defer
from twisted.python.failure import Failure
from twisted.internet.error import ProcessDone, ProcessTerminated
conch_interfaces = None
sftp = None
sftpd = None
have_pycrypto = False
@ -18,6 +19,7 @@ except ImportError:
pass
if have_pycrypto:
from twisted.conch import interfaces as conch_interfaces
from twisted.conch.ssh import filetransfer as sftp
from allmydata.frontends import sftpd
@ -669,6 +671,24 @@ class Handler(GridTestMixin, ShouldFailMixin, unittest.TestCase):
d.addCallback(lambda ign: self.root.get(u"zerolength"))
d.addCallback(lambda node: download_to_data(node))
d.addCallback(lambda data: self.failUnlessReallyEqual(data, ""))
# test WRITE | CREAT | EXCL | APPEND
d.addCallback(lambda ign:
self.handler.openFile("exclappend", sftp.FXF_WRITE | sftp.FXF_CREAT |
sftp.FXF_EXCL | sftp.FXF_APPEND, {}))
def _write_excl_append(wf):
d2 = self.root.get(u"exclappend")
d2.addCallback(lambda node: download_to_data(node))
d2.addCallback(lambda data: self.failUnlessReallyEqual(data, ""))
d2 = wf.writeChunk(10, "0123456789")
d2.addCallback(wf.writeChunk(5, "01234"))
d2.addCallback(lambda ign: wf.close())
return d2
d.addCallback(_write_excl_append)
d.addCallback(lambda ign: self.root.get(u"exclappend"))
d.addCallback(lambda node: download_to_data(node))
d.addCallback(lambda data: self.failUnlessReallyEqual(data, "012345678901234"))
"""
# test WRITE | CREAT without TRUNC
@ -917,32 +937,45 @@ class Handler(GridTestMixin, ShouldFailMixin, unittest.TestCase):
self.reason = None
def write(self, data):
self.output += data
return defer.succeed(None)
def processEnded(self, reason):
self.reason = reason
protocol_df = FakeProtocol()
protocol_error = FakeProtocol()
protocol_shell = FakeProtocol()
return defer.succeed(None)
d = self._set_up("execCommand_and_openShell")
d.addCallback(lambda ign: self.handler.execCommand(protocol_df, "df -P -k /"))
d.addCallback(lambda ign: self.failUnlessIn("1024-blocks", protocol_df.output))
d.addCallback(lambda ign: self.failUnless(isinstance(protocol_df.reason.value, ProcessDone)))
d.addCallback(lambda ign: self.handler.eofReceived())
d.addCallback(lambda ign: self.handler.closed())
d.addCallback(lambda ign: conch_interfaces.ISession(self.handler))
def _exec_df(session):
protocol = FakeProtocol()
d2 = session.execCommand(protocol, "df -P -k /")
d2.addCallback(lambda ign: self.failUnlessIn("1024-blocks", protocol.output))
d2.addCallback(lambda ign: self.failUnless(isinstance(protocol.reason.value, ProcessDone)))
d2.addCallback(lambda ign: session.eofReceived())
d2.addCallback(lambda ign: session.closed())
return d2
d.addCallback(_exec_df)
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))
d.addCallback(lambda ign: self.handler.closed())
d.addCallback(lambda ign: conch_interfaces.ISession(self.handler))
def _exec_error(session):
protocol = FakeProtocol()
d2 = session.execCommand(protocol, "error")
d2.addCallback(lambda ign: self.failUnlessEqual("", protocol.output))
d2.addCallback(lambda ign: self.failUnless(isinstance(protocol.reason.value, ProcessTerminated)))
d2.addCallback(lambda ign: self.failUnlessEqual(protocol.reason.value.exitCode, 1))
d2.addCallback(lambda ign: session.closed())
return d2
d.addCallback(_exec_error)
d.addCallback(lambda ign: self.handler.openShell(protocol_shell))
d.addCallback(lambda ign: self.failUnlessIn("only SFTP", protocol_shell.output))
d.addCallback(lambda ign: self.failUnless(isinstance(protocol_shell.reason.value, ProcessTerminated)))
d.addCallback(lambda ign: self.failUnlessEqual(protocol_shell.reason.value.exitCode, 1))
d.addCallback(lambda ign: self.handler.closed())
d.addCallback(lambda ign: conch_interfaces.ISession(self.handler))
def _openShell(session):
protocol = FakeProtocol()
d2 = session.openShell(protocol)
d2.addCallback(lambda ign: self.failUnlessIn("only SFTP", protocol.output))
d2.addCallback(lambda ign: self.failUnless(isinstance(protocol.reason.value, ProcessTerminated)))
d2.addCallback(lambda ign: self.failUnlessEqual(protocol.reason.value.exitCode, 1))
d2.addCallback(lambda ign: session.closed())
return d2
d.addCallback(_exec_error)
return d