From 6a2b0229f0753f001c989e04882441386d13446f Mon Sep 17 00:00:00 2001 From: david-sarah Date: Sun, 16 May 2010 08:43:47 -0700 Subject: [PATCH] SFTP: Support statvfs extensions, avoid logging actual data, and decline shell sessions politely. --- src/allmydata/frontends/sftpd.py | 36 ++++++++++++++++++---------- src/allmydata/test/test_sftp.py | 40 ++++++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/allmydata/frontends/sftpd.py b/src/allmydata/frontends/sftpd.py index b36d29b8..e7f98633 100644 --- a/src/allmydata/frontends/sftpd.py +++ b/src/allmydata/frontends/sftpd.py @@ -1,5 +1,5 @@ -import os, tempfile, heapq, binascii, traceback, array, stat +import os, tempfile, heapq, binascii, traceback, array, stat, struct from stat import S_IFREG, S_IFDIR from time import time, strftime, localtime @@ -41,17 +41,15 @@ warnings.filterwarnings("ignore", category=DeprecationWarning, noisy = True use_foolscap_logging = True +from allmydata.util.log import NOISY, OPERATIONAL, SCARY + if use_foolscap_logging: - from allmydata.util.log import msg as logmsg, err as logerr, \ - NOISY, OPERATIONAL, SCARY, PrefixingLogMixin + from allmydata.util.log import msg as logmsg, err as logerr, PrefixingLogMixin else: def logmsg(s, level=None): print s def logerr(s, level=None): print s - NOISY = None - OPERATIONAL = None - SCARY = None class PrefixingLogMixin: def __init__(self, facility=None): pass @@ -290,7 +288,7 @@ class EncryptedTemporaryFile(PrefixingLogMixin): return plaintext def write(self, plaintext): - if noisy: self.log(".write(%r)" % (plaintext,), level=NOISY) + if noisy: self.log(".write()" % (len(plaintext),), level=NOISY) index = self.file.tell() ciphertext = self._crypt(index, plaintext) self.file.write(ciphertext) @@ -357,7 +355,7 @@ class OverwriteableFileConsumer(PrefixingLogMixin): p.resumeProducing() def write(self, data): - if noisy: self.log(".write(%r)" % (data,), level=NOISY) + if noisy: self.log(".write()" % (len(data),), level=NOISY) if self.check_abort(): self.close() return @@ -833,7 +831,8 @@ class SFTPUserHandler(ConchUser, PrefixingLogMixin): def openShell(self, protocol): self.log(".openShell(%r)" % (protocol,), level=OPERATIONAL) - raise NotImplementedError + 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) @@ -1167,9 +1166,22 @@ class SFTPUserHandler(ConchUser, PrefixingLogMixin): def extendedRequest(self, extendedName, extendedData): self.log(".extendedRequest(%r, %r)" % (extendedName, extendedData), level=OPERATIONAL) - # A client 'df' command requires the 'statvfs@openssh.com' extension, - # but there's little point to implementing that since we would only - # have faked values to report. + if extendedName == 'statvfs@openssh.com' or extendedName == 'fstatvfs@openssh.com': + # + return struct.pack('>QQQQQQQQQQQ', + 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) */ + 314159265, # uint64 f_bfree /* free blocks in file system */ + 314159265, # uint64 f_bavail /* free blocks for non-root */ + 200000000, # uint64 f_files /* total file inodes */ + 100000000, # uint64 f_ffree /* free file inodes */ + 100000000, # uint64 f_favail /* free file inodes for non-root */ + 0x1AF5, # uint64 f_fsid /* file system id */ + 2, # uint64 f_flag /* bit mask = ST_NOSUID; not ST_RDONLY */ + 65535, # uint64 f_namemax /* maximum filename length */ + ) + raise SFTPError(FX_OP_UNSUPPORTED, "extendedRequest %r" % extendedName) def realPath(self, pathstring): diff --git a/src/allmydata/test/test_sftp.py b/src/allmydata/test/test_sftp.py index 418ba09d..919c6806 100644 --- a/src/allmydata/test/test_sftp.py +++ b/src/allmydata/test/test_sftp.py @@ -266,9 +266,6 @@ class Handler(GridTestMixin, ShouldFailMixin, unittest.TestCase): d.addCallback(lambda ign: self.shouldFailWithSFTPError(sftp.FX_OP_UNSUPPORTED, "makeLink link file", self.handler.makeLink, "link", "file")) - d.addCallback(lambda ign: - self.shouldFailWithSFTPError(sftp.FX_OP_UNSUPPORTED, "extendedRequest foo bar", - self.handler.extendedRequest, "foo", "bar")) return d @@ -913,7 +910,7 @@ class Handler(GridTestMixin, ShouldFailMixin, unittest.TestCase): self.handler.makeDirectory, "small", {})) return d - def test_execCommand(self): + def test_execCommand_and_openShell(self): class FakeProtocol: def __init__(self): self.output = "" @@ -923,20 +920,43 @@ class Handler(GridTestMixin, ShouldFailMixin, unittest.TestCase): def processEnded(self, reason): self.reason = reason - protocol_ok = FakeProtocol() + protocol_df = FakeProtocol() protocol_error = FakeProtocol() + protocol_shell = FakeProtocol() - d = self._set_up("execCommand") + d = self._set_up("execCommand_and_openShell") - 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.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: self.handler.execCommand(protocol_error, "error")) - d.addCallback(lambda ign: self.failUnlessEqual(protocol_error.output, "")) + 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: 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()) + + return d + + def test_extendedRequest(self): + d = self._set_up("extendedRequest") + + d.addCallback(lambda ign: self.handler.extendedRequest("statvfs@openssh.com", "/")) + def _check(res): + self.failUnless(isinstance(res, str)) + self.failUnlessEqual(len(res), 8*11) + d.addCallback(_check) + + d.addCallback(lambda ign: + self.shouldFailWithSFTPError(sftp.FX_OP_UNSUPPORTED, "extendedRequest foo bar", + self.handler.extendedRequest, "foo", "bar")) return d \ No newline at end of file -- 2.45.2