| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 # -*- test-case-name: twisted.conch.test.test_cftp -*- |  | 
| 2 # Copyright (c) 2001-2008 Twisted Matrix Laboratories. |  | 
| 3 # See LICENSE file for details. |  | 
| 4 |  | 
| 5 import sys, os |  | 
| 6 |  | 
| 7 try: |  | 
| 8     import Crypto |  | 
| 9 except ImportError: |  | 
| 10     Crypto = None |  | 
| 11 |  | 
| 12 try: |  | 
| 13     from twisted.conch import unix |  | 
| 14     from twisted.conch.scripts import cftp |  | 
| 15     from twisted.conch.client import connect, default, options |  | 
| 16     from twisted.conch.test.test_filetransfer import FileTransferForTestAvatar |  | 
| 17 except ImportError: |  | 
| 18     unix = None |  | 
| 19     try: |  | 
| 20         del sys.modules['twisted.conch.unix'] # remove the bad import |  | 
| 21     except KeyError: |  | 
| 22         # In Python 2.4, the bad import has already been cleaned up for us. |  | 
| 23         pass |  | 
| 24 |  | 
| 25 from twisted.cred import portal |  | 
| 26 from twisted.internet import reactor, protocol, interfaces, defer, error |  | 
| 27 from twisted.internet.utils import getProcessOutputAndValue |  | 
| 28 from twisted.python import log |  | 
| 29 |  | 
| 30 from twisted.conch.test import test_ssh, test_conch |  | 
| 31 from twisted.conch.test.test_filetransfer import SFTPTestBase |  | 
| 32 from twisted.conch.test.test_filetransfer import FileTransferTestAvatar |  | 
| 33 |  | 
| 34 |  | 
| 35 class FileTransferTestRealm: |  | 
| 36     def __init__(self, testDir): |  | 
| 37         self.testDir = testDir |  | 
| 38 |  | 
| 39     def requestAvatar(self, avatarID, mind, *interfaces): |  | 
| 40         a = FileTransferTestAvatar(self.testDir) |  | 
| 41         return interfaces[0], a, lambda: None |  | 
| 42 |  | 
| 43 |  | 
| 44 class SFTPTestProcess(protocol.ProcessProtocol): |  | 
| 45     """ |  | 
| 46     Protocol for testing cftp. Provides an interface between Python (where all |  | 
| 47     the tests are) and the cftp client process (which does the work that is |  | 
| 48     being tested). |  | 
| 49     """ |  | 
| 50 |  | 
| 51     def __init__(self, onOutReceived): |  | 
| 52         """ |  | 
| 53         @param onOutReceived: A L{Deferred} to be fired as soon as data is |  | 
| 54         received from stdout. |  | 
| 55         """ |  | 
| 56         self.clearBuffer() |  | 
| 57         self.onOutReceived = onOutReceived |  | 
| 58         self.onProcessEnd = None |  | 
| 59         self._expectingCommand = None |  | 
| 60         self._processEnded = False |  | 
| 61 |  | 
| 62     def clearBuffer(self): |  | 
| 63         """ |  | 
| 64         Clear any buffered data received from stdout. Should be private. |  | 
| 65         """ |  | 
| 66         self.buffer = '' |  | 
| 67         self._linesReceived = [] |  | 
| 68         self._lineBuffer = '' |  | 
| 69 |  | 
| 70     def outReceived(self, data): |  | 
| 71         """ |  | 
| 72         Called by Twisted when the cftp client prints data to stdout. |  | 
| 73         """ |  | 
| 74         log.msg('got %s' % data) |  | 
| 75         lines = (self._lineBuffer + data).split('\n') |  | 
| 76         self._lineBuffer = lines.pop(-1) |  | 
| 77         self._linesReceived.extend(lines) |  | 
| 78         # XXX - not strictly correct. |  | 
| 79         # We really want onOutReceived to fire after the first 'cftp>' prompt |  | 
| 80         # has been received. (See use in TestOurServerCmdLineClient.setUp) |  | 
| 81         if self.onOutReceived is not None: |  | 
| 82             d, self.onOutReceived = self.onOutReceived, None |  | 
| 83             d.callback(data) |  | 
| 84         self.buffer += data |  | 
| 85         self._checkForCommand() |  | 
| 86 |  | 
| 87     def _checkForCommand(self): |  | 
| 88         prompt = 'cftp> ' |  | 
| 89         if self._expectingCommand and self._lineBuffer == prompt: |  | 
| 90             buf = '\n'.join(self._linesReceived) |  | 
| 91             if buf.startswith(prompt): |  | 
| 92                 buf = buf[len(prompt):] |  | 
| 93             self.clearBuffer() |  | 
| 94             d, self._expectingCommand = self._expectingCommand, None |  | 
| 95             d.callback(buf) |  | 
| 96 |  | 
| 97     def errReceived(self, data): |  | 
| 98         """ |  | 
| 99         Called by Twisted when the cftp client prints data to stderr. |  | 
| 100         """ |  | 
| 101         log.msg('err: %s' % data) |  | 
| 102 |  | 
| 103     def getBuffer(self): |  | 
| 104         """ |  | 
| 105         Return the contents of the buffer of data received from stdout. |  | 
| 106         """ |  | 
| 107         return self.buffer |  | 
| 108 |  | 
| 109     def runCommand(self, command): |  | 
| 110         """ |  | 
| 111         Issue the given command via the cftp client. Return a C{Deferred} that |  | 
| 112         fires when the server returns a result. Note that the C{Deferred} will |  | 
| 113         callback even if the server returns some kind of error. |  | 
| 114 |  | 
| 115         @param command: A string containing an sftp command. |  | 
| 116 |  | 
| 117         @return: A C{Deferred} that fires when the sftp server returns a |  | 
| 118         result. The payload is the server's response string. |  | 
| 119         """ |  | 
| 120         self._expectingCommand = defer.Deferred() |  | 
| 121         self.clearBuffer() |  | 
| 122         self.transport.write(command + '\n') |  | 
| 123         return self._expectingCommand |  | 
| 124 |  | 
| 125     def runScript(self, commands): |  | 
| 126         """ |  | 
| 127         Run each command in sequence and return a Deferred that fires when all |  | 
| 128         commands are completed. |  | 
| 129 |  | 
| 130         @param commands: A list of strings containing sftp commands. |  | 
| 131 |  | 
| 132         @return: A C{Deferred} that fires when all commands are completed. The |  | 
| 133         payload is a list of response strings from the server, in the same |  | 
| 134         order as the commands. |  | 
| 135         """ |  | 
| 136         sem = defer.DeferredSemaphore(1) |  | 
| 137         dl = [sem.run(self.runCommand, command) for command in commands] |  | 
| 138         return defer.gatherResults(dl) |  | 
| 139 |  | 
| 140     def killProcess(self): |  | 
| 141         """ |  | 
| 142         Kill the process if it is still running. |  | 
| 143 |  | 
| 144         If the process is still running, sends a KILL signal to the transport |  | 
| 145         and returns a C{Deferred} which fires when L{processEnded} is called. |  | 
| 146 |  | 
| 147         @return: a C{Deferred}. |  | 
| 148         """ |  | 
| 149         if self._processEnded: |  | 
| 150             return defer.succeed(None) |  | 
| 151         self.onProcessEnd = defer.Deferred() |  | 
| 152         self.transport.signalProcess('KILL') |  | 
| 153         return self.onProcessEnd |  | 
| 154 |  | 
| 155     def processEnded(self, reason): |  | 
| 156         """ |  | 
| 157         Called by Twisted when the cftp client process ends. |  | 
| 158         """ |  | 
| 159         self._processEnded = True |  | 
| 160         if self.onProcessEnd: |  | 
| 161             d, self.onProcessEnd = self.onProcessEnd, None |  | 
| 162             d.callback(None) |  | 
| 163 |  | 
| 164 |  | 
| 165 class CFTPClientTestBase(SFTPTestBase): |  | 
| 166     def setUp(self): |  | 
| 167         f = open('dsa_test.pub','w') |  | 
| 168         f.write(test_ssh.publicDSA_openssh) |  | 
| 169         f.close() |  | 
| 170         f = open('dsa_test','w') |  | 
| 171         f.write(test_ssh.privateDSA_openssh) |  | 
| 172         f.close() |  | 
| 173         os.chmod('dsa_test', 33152) |  | 
| 174         f = open('kh_test','w') |  | 
| 175         f.write('127.0.0.1 ' + test_ssh.publicRSA_openssh) |  | 
| 176         f.close() |  | 
| 177         return SFTPTestBase.setUp(self) |  | 
| 178 |  | 
| 179     def startServer(self): |  | 
| 180         realm = FileTransferTestRealm(self.testDir) |  | 
| 181         p = portal.Portal(realm) |  | 
| 182         p.registerChecker(test_ssh.ConchTestPublicKeyChecker()) |  | 
| 183         fac = test_ssh.ConchTestServerFactory() |  | 
| 184         fac.portal = p |  | 
| 185         self.server = reactor.listenTCP(0, fac, interface="127.0.0.1") |  | 
| 186 |  | 
| 187     def stopServer(self): |  | 
| 188         if not hasattr(self.server.factory, 'proto'): |  | 
| 189             return self._cbStopServer(None) |  | 
| 190         self.server.factory.proto.expectedLoseConnection = 1 |  | 
| 191         d = defer.maybeDeferred( |  | 
| 192             self.server.factory.proto.transport.loseConnection) |  | 
| 193         d.addCallback(self._cbStopServer) |  | 
| 194         return d |  | 
| 195 |  | 
| 196     def _cbStopServer(self, ignored): |  | 
| 197         return defer.maybeDeferred(self.server.stopListening) |  | 
| 198 |  | 
| 199     def tearDown(self): |  | 
| 200         for f in ['dsa_test.pub', 'dsa_test', 'kh_test']: |  | 
| 201             try: |  | 
| 202                 os.remove(f) |  | 
| 203             except: |  | 
| 204                 pass |  | 
| 205         return SFTPTestBase.tearDown(self) |  | 
| 206 |  | 
| 207 |  | 
| 208 |  | 
| 209 class TestOurServerCmdLineClient(CFTPClientTestBase): |  | 
| 210 |  | 
| 211     def setUp(self): |  | 
| 212         CFTPClientTestBase.setUp(self) |  | 
| 213 |  | 
| 214         self.startServer() |  | 
| 215         cmds = ('-p %i -l testuser ' |  | 
| 216                '--known-hosts kh_test ' |  | 
| 217                '--user-authentications publickey ' |  | 
| 218                '--host-key-algorithms ssh-rsa ' |  | 
| 219                '-K direct ' |  | 
| 220                '-i dsa_test ' |  | 
| 221                '-a --nocache ' |  | 
| 222                '-v ' |  | 
| 223                '127.0.0.1') |  | 
| 224         port = self.server.getHost().port |  | 
| 225         cmds = test_conch._makeArgs((cmds % port).split(), mod='cftp') |  | 
| 226         log.msg('running %s %s' % (sys.executable, cmds)) |  | 
| 227         d = defer.Deferred() |  | 
| 228         self.processProtocol = SFTPTestProcess(d) |  | 
| 229         d.addCallback(lambda _: self.processProtocol.clearBuffer()) |  | 
| 230         env = os.environ.copy() |  | 
| 231         env['PYTHONPATH'] = os.pathsep.join(sys.path) |  | 
| 232         reactor.spawnProcess(self.processProtocol, sys.executable, cmds, |  | 
| 233                              env=env) |  | 
| 234         return d |  | 
| 235 |  | 
| 236     def tearDown(self): |  | 
| 237         d = self.stopServer() |  | 
| 238         d.addCallback(lambda _: self.processProtocol.killProcess()) |  | 
| 239         return d |  | 
| 240 |  | 
| 241     def _killProcess(self, ignored): |  | 
| 242         try: |  | 
| 243             self.processProtocol.transport.signalProcess('KILL') |  | 
| 244         except error.ProcessExitedAlready: |  | 
| 245             pass |  | 
| 246 |  | 
| 247     def runCommand(self, command): |  | 
| 248         """ |  | 
| 249         Run the given command with the cftp client. Return a C{Deferred} that |  | 
| 250         fires when the command is complete. Payload is the server's output for |  | 
| 251         that command. |  | 
| 252         """ |  | 
| 253         return self.processProtocol.runCommand(command) |  | 
| 254 |  | 
| 255     def runScript(self, *commands): |  | 
| 256         """ |  | 
| 257         Run the given commands with the cftp client. Returns a C{Deferred} |  | 
| 258         that fires when the commands are all complete. The C{Deferred}'s |  | 
| 259         payload is a list of output for each command. |  | 
| 260         """ |  | 
| 261         return self.processProtocol.runScript(commands) |  | 
| 262 |  | 
| 263     def testCdPwd(self): |  | 
| 264         """ |  | 
| 265         Test that 'pwd' reports the current remote directory, that 'lpwd' |  | 
| 266         reports the current local directory, and that changing to a |  | 
| 267         subdirectory then changing to its parent leaves you in the original |  | 
| 268         remote directory. |  | 
| 269         """ |  | 
| 270         # XXX - not actually a unit test, see docstring. |  | 
| 271         homeDir = os.path.join(os.getcwd(), self.testDir) |  | 
| 272         d = self.runScript('pwd', 'lpwd', 'cd testDirectory', 'cd ..', 'pwd') |  | 
| 273         d.addCallback(lambda xs: xs[:3] + xs[4:]) |  | 
| 274         d.addCallback(self.assertEqual, |  | 
| 275                       [homeDir, os.getcwd(), '', homeDir]) |  | 
| 276         return d |  | 
| 277 |  | 
| 278     def testChAttrs(self): |  | 
| 279         """ |  | 
| 280         Check that 'ls -l' output includes the access permissions and that |  | 
| 281         this output changes appropriately with 'chmod'. |  | 
| 282         """ |  | 
| 283         def _check(results): |  | 
| 284             self.flushLoggedErrors() |  | 
| 285             self.assertTrue(results[0].startswith('-rw-r--r--')) |  | 
| 286             self.assertEqual(results[1], '') |  | 
| 287             self.assertTrue(results[2].startswith('----------'), results[2]) |  | 
| 288             self.assertEqual(results[3], '') |  | 
| 289 |  | 
| 290         d = self.runScript('ls -l testfile1', 'chmod 0 testfile1', |  | 
| 291                            'ls -l testfile1', 'chmod 644 testfile1') |  | 
| 292         return d.addCallback(_check) |  | 
| 293         # XXX test chgrp/own |  | 
| 294 |  | 
| 295 |  | 
| 296     def testList(self): |  | 
| 297         """ |  | 
| 298         Check 'ls' works as expected. Checks for wildcards, hidden files, |  | 
| 299         listing directories and listing empty directories. |  | 
| 300         """ |  | 
| 301         def _check(results): |  | 
| 302             self.assertEqual(results[0], ['testDirectory', 'testRemoveFile', |  | 
| 303                                           'testRenameFile', 'testfile1']) |  | 
| 304             self.assertEqual(results[1], ['testDirectory', 'testRemoveFile', |  | 
| 305                                           'testRenameFile', 'testfile1']) |  | 
| 306             self.assertEqual(results[2], ['testRemoveFile', 'testRenameFile']) |  | 
| 307             self.assertEqual(results[3], ['.testHiddenFile', 'testRemoveFile', |  | 
| 308                                           'testRenameFile']) |  | 
| 309             self.assertEqual(results[4], ['']) |  | 
| 310         d = self.runScript('ls', 'ls ../' + os.path.basename(self.testDir), |  | 
| 311                            'ls *File', 'ls -a *File', 'ls -l testDirectory') |  | 
| 312         d.addCallback(lambda xs: [x.split('\n') for x in xs]) |  | 
| 313         return d.addCallback(_check) |  | 
| 314 |  | 
| 315     def testHelp(self): |  | 
| 316         """ |  | 
| 317         Check that running the '?' command returns help. |  | 
| 318         """ |  | 
| 319         d = self.runCommand('?') |  | 
| 320         d.addCallback(self.assertEqual, |  | 
| 321                       cftp.StdioClient(None).cmd_HELP('').strip()) |  | 
| 322         return d |  | 
| 323 |  | 
| 324     def assertFilesEqual(self, name1, name2, msg=None): |  | 
| 325         """ |  | 
| 326         Assert that the files at C{name1} and C{name2} contain exactly the |  | 
| 327         same data. |  | 
| 328         """ |  | 
| 329         f1 = file(name1).read() |  | 
| 330         f2 = file(name2).read() |  | 
| 331         self.failUnlessEqual(f1, f2, msg) |  | 
| 332 |  | 
| 333 |  | 
| 334     def testGet(self): |  | 
| 335         """ |  | 
| 336         Test that 'get' saves the remote file to the correct local location, |  | 
| 337         that the output of 'get' is correct and that 'rm' actually removes |  | 
| 338         the file. |  | 
| 339         """ |  | 
| 340         # XXX - not actually a unit test |  | 
| 341         expectedOutput = ("Transferred %s/%s/testfile1 to %s/test file2" |  | 
| 342                           % (os.getcwd(), self.testDir, self.testDir)) |  | 
| 343         def _checkGet(result): |  | 
| 344             self.assertTrue(result.endswith(expectedOutput)) |  | 
| 345             self.assertFilesEqual(self.testDir + '/testfile1', |  | 
| 346                                   self.testDir + '/test file2', |  | 
| 347                                   "get failed") |  | 
| 348             return self.runCommand('rm "test file2"') |  | 
| 349 |  | 
| 350         d = self.runCommand('get testfile1 "%s/test file2"' % (self.testDir,)) |  | 
| 351         d.addCallback(_checkGet) |  | 
| 352         d.addCallback(lambda _: self.failIf( |  | 
| 353             os.path.exists(self.testDir + '/test file2'))) |  | 
| 354         return d |  | 
| 355 |  | 
| 356 |  | 
| 357     def testWildcardGet(self): |  | 
| 358         """ |  | 
| 359         Test that 'get' works correctly when given wildcard parameters. |  | 
| 360         """ |  | 
| 361         def _check(ignored): |  | 
| 362             self.assertFilesEqual(self.testDir + '/testRemoveFile', |  | 
| 363                                   'testRemoveFile', |  | 
| 364                                   'testRemoveFile get failed') |  | 
| 365             self.assertFilesEqual(self.testDir + '/testRenameFile', |  | 
| 366                                   'testRenameFile', |  | 
| 367                                   'testRenameFile get failed') |  | 
| 368 |  | 
| 369         d = self.runCommand('get testR*') |  | 
| 370         return d.addCallback(_check) |  | 
| 371 |  | 
| 372 |  | 
| 373     def testPut(self): |  | 
| 374         """ |  | 
| 375         Check that 'put' uploads files correctly and that they can be |  | 
| 376         successfully removed. Also check the output of the put command. |  | 
| 377         """ |  | 
| 378         # XXX - not actually a unit test |  | 
| 379         expectedOutput = ('Transferred %s/testfile1 to %s/%s/test"file2' |  | 
| 380                           % (self.testDir, os.getcwd(), self.testDir)) |  | 
| 381         def _checkPut(result): |  | 
| 382             self.assertFilesEqual(self.testDir + '/testfile1', |  | 
| 383                                   self.testDir + '/test"file2') |  | 
| 384             self.failUnless(result.endswith(expectedOutput)) |  | 
| 385             return self.runCommand('rm "test\\"file2"') |  | 
| 386 |  | 
| 387         d = self.runCommand('put %s/testfile1 "test\\"file2"' |  | 
| 388                             % (self.testDir,)) |  | 
| 389         d.addCallback(_checkPut) |  | 
| 390         d.addCallback(lambda _: self.failIf( |  | 
| 391             os.path.exists(self.testDir + '/test"file2'))) |  | 
| 392         return d |  | 
| 393 |  | 
| 394 |  | 
| 395     def testWildcardPut(self): |  | 
| 396         """ |  | 
| 397         What happens if you issue a 'put' command and include a wildcard (i.e. |  | 
| 398         '*') in parameter? Check that all files matching the wildcard are |  | 
| 399         uploaded to the correct directory. |  | 
| 400         """ |  | 
| 401         def check(results): |  | 
| 402             self.assertEqual(results[0], '') |  | 
| 403             self.assertEqual(results[2], '') |  | 
| 404             self.assertFilesEqual(self.testDir + '/testRemoveFile', |  | 
| 405                                   self.testDir + '/../testRemoveFile', |  | 
| 406                                   'testRemoveFile get failed') |  | 
| 407             self.assertFilesEqual(self.testDir + '/testRenameFile', |  | 
| 408                                   self.testDir + '/../testRenameFile', |  | 
| 409                                   'testRenameFile get failed') |  | 
| 410 |  | 
| 411         d = self.runScript('cd ..', |  | 
| 412                            'put %s/testR*' % (self.testDir,), |  | 
| 413                            'cd %s' % os.path.basename(self.testDir)) |  | 
| 414         d.addCallback(check) |  | 
| 415         return d |  | 
| 416 |  | 
| 417 |  | 
| 418     def testLink(self): |  | 
| 419         """ |  | 
| 420         Test that 'ln' creates a file which appears as a link in the output of |  | 
| 421         'ls'. Check that removing the new file succeeds without output. |  | 
| 422         """ |  | 
| 423         def _check(results): |  | 
| 424             self.flushLoggedErrors() |  | 
| 425             self.assertEqual(results[0], '') |  | 
| 426             self.assertTrue(results[1].startswith('l'), 'link failed') |  | 
| 427             return self.runCommand('rm testLink') |  | 
| 428 |  | 
| 429         d = self.runScript('ln testLink testfile1', 'ls -l testLink') |  | 
| 430         d.addCallback(_check) |  | 
| 431         d.addCallback(self.assertEqual, '') |  | 
| 432         return d |  | 
| 433 |  | 
| 434 |  | 
| 435     def testRemoteDirectory(self): |  | 
| 436         """ |  | 
| 437         Test that we can create and remove directories with the cftp client. |  | 
| 438         """ |  | 
| 439         def _check(results): |  | 
| 440             self.assertEqual(results[0], '') |  | 
| 441             self.assertTrue(results[1].startswith('d')) |  | 
| 442             return self.runCommand('rmdir testMakeDirectory') |  | 
| 443 |  | 
| 444         d = self.runScript('mkdir testMakeDirectory', |  | 
| 445                            'ls -l testMakeDirector?') |  | 
| 446         d.addCallback(_check) |  | 
| 447         d.addCallback(self.assertEqual, '') |  | 
| 448         return d |  | 
| 449 |  | 
| 450 |  | 
| 451     def testLocalDirectory(self): |  | 
| 452         """ |  | 
| 453         Test that we can create a directory locally and remove it with the |  | 
| 454         cftp client. This test works because the 'remote' server is running |  | 
| 455         out of a local directory. |  | 
| 456         """ |  | 
| 457         d = self.runCommand('lmkdir %s/testLocalDirectory' % (self.testDir,)) |  | 
| 458         d.addCallback(self.assertEqual, '') |  | 
| 459         d.addCallback(lambda _: self.runCommand('rmdir testLocalDirectory')) |  | 
| 460         d.addCallback(self.assertEqual, '') |  | 
| 461         return d |  | 
| 462 |  | 
| 463 |  | 
| 464     def testRename(self): |  | 
| 465         """ |  | 
| 466         Test that we can rename a file. |  | 
| 467         """ |  | 
| 468         def _check(results): |  | 
| 469             self.assertEqual(results[0], '') |  | 
| 470             self.assertEqual(results[1], 'testfile2') |  | 
| 471             return self.runCommand('rename testfile2 testfile1') |  | 
| 472 |  | 
| 473         d = self.runScript('rename testfile1 testfile2', 'ls testfile?') |  | 
| 474         d.addCallback(_check) |  | 
| 475         d.addCallback(self.assertEqual, '') |  | 
| 476         return d |  | 
| 477 |  | 
| 478 |  | 
| 479     def testCommand(self): |  | 
| 480         d = self.runCommand('!echo hello') |  | 
| 481         return d.addCallback(self.assertEqual, 'hello') |  | 
| 482 |  | 
| 483 |  | 
| 484 class TestOurServerBatchFile(CFTPClientTestBase): |  | 
| 485     def setUp(self): |  | 
| 486         CFTPClientTestBase.setUp(self) |  | 
| 487         self.startServer() |  | 
| 488 |  | 
| 489     def tearDown(self): |  | 
| 490         CFTPClientTestBase.tearDown(self) |  | 
| 491         return self.stopServer() |  | 
| 492 |  | 
| 493     def _getBatchOutput(self, f): |  | 
| 494         fn = self.mktemp() |  | 
| 495         open(fn, 'w').write(f) |  | 
| 496         l = [] |  | 
| 497         port = self.server.getHost().port |  | 
| 498         cmds = ('-p %i -l testuser ' |  | 
| 499                     '--known-hosts kh_test ' |  | 
| 500                     '--user-authentications publickey ' |  | 
| 501                     '--host-key-algorithms ssh-rsa ' |  | 
| 502                     '-K direct ' |  | 
| 503                     '-i dsa_test ' |  | 
| 504                     '-a --nocache ' |  | 
| 505                     '-v -b %s 127.0.0.1') % (port, fn) |  | 
| 506         cmds = test_conch._makeArgs(cmds.split(), mod='cftp')[1:] |  | 
| 507         log.msg('running %s %s' % (sys.executable, cmds)) |  | 
| 508         env = os.environ.copy() |  | 
| 509         env['PYTHONPATH'] = os.pathsep.join(sys.path) |  | 
| 510 |  | 
| 511         self.server.factory.expectedLoseConnection = 1 |  | 
| 512 |  | 
| 513         d = getProcessOutputAndValue(sys.executable, cmds, env=env) |  | 
| 514 |  | 
| 515         def _cleanup(res): |  | 
| 516             os.remove(fn) |  | 
| 517             return res |  | 
| 518 |  | 
| 519         d.addCallback(lambda res: res[0]) |  | 
| 520         d.addBoth(_cleanup) |  | 
| 521 |  | 
| 522         return d |  | 
| 523 |  | 
| 524     def testBatchFile(self): |  | 
| 525         """Test whether batch file function of cftp ('cftp -b batchfile'). |  | 
| 526         This works by treating the file as a list of commands to be run. |  | 
| 527         """ |  | 
| 528         cmds = """pwd |  | 
| 529 ls |  | 
| 530 exit |  | 
| 531 """ |  | 
| 532         def _cbCheckResult(res): |  | 
| 533             res = res.split('\n') |  | 
| 534             log.msg('RES %s' % str(res)) |  | 
| 535             self.failUnless(res[1].find(self.testDir) != -1, repr(res)) |  | 
| 536             self.failUnlessEqual(res[3:-2], ['testDirectory', 'testRemoveFile', |  | 
| 537                                              'testRenameFile', 'testfile1']) |  | 
| 538 |  | 
| 539         d = self._getBatchOutput(cmds) |  | 
| 540         d.addCallback(_cbCheckResult) |  | 
| 541         return d |  | 
| 542 |  | 
| 543     def testError(self): |  | 
| 544         """Test that an error in the batch file stops running the batch. |  | 
| 545         """ |  | 
| 546         cmds = """chown 0 missingFile |  | 
| 547 pwd |  | 
| 548 exit |  | 
| 549 """ |  | 
| 550         def _cbCheckResult(res): |  | 
| 551             self.failIf(res.find(self.testDir) != -1) |  | 
| 552 |  | 
| 553         d = self._getBatchOutput(cmds) |  | 
| 554         d.addCallback(_cbCheckResult) |  | 
| 555         return d |  | 
| 556 |  | 
| 557     def testIgnoredError(self): |  | 
| 558         """Test that a minus sign '-' at the front of a line ignores |  | 
| 559         any errors. |  | 
| 560         """ |  | 
| 561         cmds = """-chown 0 missingFile |  | 
| 562 pwd |  | 
| 563 exit |  | 
| 564 """ |  | 
| 565         def _cbCheckResult(res): |  | 
| 566             self.failIf(res.find(self.testDir) == -1) |  | 
| 567 |  | 
| 568         d = self._getBatchOutput(cmds) |  | 
| 569         d.addCallback(_cbCheckResult) |  | 
| 570         return d |  | 
| 571 |  | 
| 572 |  | 
| 573 class TestOurServerUnixClient(test_conch._UnixFixHome, CFTPClientTestBase): |  | 
| 574 |  | 
| 575     def setUp(self): |  | 
| 576         test_conch._UnixFixHome.setUp(self) |  | 
| 577         CFTPClientTestBase.setUp(self) |  | 
| 578         self.startServer() |  | 
| 579         cmd1 = ('-p %i -l testuser ' |  | 
| 580                 '--known-hosts kh_test ' |  | 
| 581                 '--host-key-algorithms ssh-rsa ' |  | 
| 582                 '-a ' |  | 
| 583                 '-K direct ' |  | 
| 584                 '-i dsa_test ' |  | 
| 585                 '127.0.0.1' |  | 
| 586                 ) |  | 
| 587         port = self.server.getHost().port |  | 
| 588         cmds1 = (cmd1 % port).split() |  | 
| 589         o = options.ConchOptions() |  | 
| 590         def _(host, *args): |  | 
| 591             o['host'] = host |  | 
| 592         o.parseArgs = _ |  | 
| 593         o.parseOptions(cmds1) |  | 
| 594         vhk = default.verifyHostKey |  | 
| 595         self.conn = conn = test_conch.SSHTestConnectionForUnix(None) |  | 
| 596         uao = default.SSHUserAuthClient(o['user'], o, conn) |  | 
| 597         return connect.connect(o['host'], int(o['port']), o, vhk, uao) |  | 
| 598 |  | 
| 599     def tearDown(self): |  | 
| 600         CFTPClientTestBase.tearDown(self) |  | 
| 601         d = defer.maybeDeferred(self.conn.transport.loseConnection) |  | 
| 602         d.addCallback(lambda x : self.stopServer()) |  | 
| 603         def clean(ign): |  | 
| 604             test_conch._UnixFixHome.tearDown(self) |  | 
| 605             return ign |  | 
| 606         return defer.gatherResults([d, self.conn.stopDeferred]).addBoth(clean) |  | 
| 607 |  | 
| 608     def _getBatchOutput(self, f): |  | 
| 609         fn = self.mktemp() |  | 
| 610         open(fn, 'w').write(f) |  | 
| 611         port = self.server.getHost().port |  | 
| 612         cmds = ('-p %i -l testuser ' |  | 
| 613                     '-K unix ' |  | 
| 614                     '-a ' |  | 
| 615                     '-v -b %s 127.0.0.1') % (port, fn) |  | 
| 616         cmds = test_conch._makeArgs(cmds.split(), mod='cftp')[1:] |  | 
| 617         log.msg('running %s %s' % (sys.executable, cmds)) |  | 
| 618         env = os.environ.copy() |  | 
| 619         env['PYTHONPATH'] = os.pathsep.join(sys.path) |  | 
| 620 |  | 
| 621         self.server.factory.expectedLoseConnection = 1 |  | 
| 622 |  | 
| 623         d = getProcessOutputAndValue(sys.executable, cmds, env=env) |  | 
| 624 |  | 
| 625         def _cleanup(res): |  | 
| 626             os.remove(fn) |  | 
| 627             return res |  | 
| 628 |  | 
| 629         d.addCallback(lambda res: res[0]) |  | 
| 630         d.addBoth(_cleanup) |  | 
| 631 |  | 
| 632         return d |  | 
| 633 |  | 
| 634     def testBatchFile(self): |  | 
| 635         """Test that the client works even over a UNIX connection. |  | 
| 636         """ |  | 
| 637         cmds = """pwd |  | 
| 638 exit |  | 
| 639 """ |  | 
| 640         d = self._getBatchOutput(cmds) |  | 
| 641         d.addCallback( |  | 
| 642             lambda res : self.failIf(res.find(self.testDir) == -1, |  | 
| 643                                      "%s not in %r" % (self.testDir, res))) |  | 
| 644         return d |  | 
| 645 |  | 
| 646 |  | 
| 647 |  | 
| 648 class TestOurServerSftpClient(CFTPClientTestBase): |  | 
| 649     """ |  | 
| 650     Test the sftp server against sftp command line client. |  | 
| 651     """ |  | 
| 652 |  | 
| 653     def setUp(self): |  | 
| 654         CFTPClientTestBase.setUp(self) |  | 
| 655         return self.startServer() |  | 
| 656 |  | 
| 657 |  | 
| 658     def tearDown(self): |  | 
| 659         return self.stopServer() |  | 
| 660 |  | 
| 661 |  | 
| 662     def test_extendedAttributes(self): |  | 
| 663         """ |  | 
| 664         Test the return of extended attributes by the server: the sftp client |  | 
| 665         should ignore them, but still be able to parse the response correctly. |  | 
| 666 |  | 
| 667         This test is mainly here to check that |  | 
| 668         L{filetransfer.FILEXFER_ATTR_EXTENDED} has the correct value. |  | 
| 669         """ |  | 
| 670         fn = self.mktemp() |  | 
| 671         open(fn, 'w').write("ls .\nexit") |  | 
| 672         port = self.server.getHost().port |  | 
| 673 |  | 
| 674         oldGetAttr = FileTransferForTestAvatar._getAttrs |  | 
| 675         def _getAttrs(self, s): |  | 
| 676             attrs = oldGetAttr(self, s) |  | 
| 677             attrs["ext_foo"] = "bar" |  | 
| 678             return attrs |  | 
| 679 |  | 
| 680         self.patch(FileTransferForTestAvatar, "_getAttrs", _getAttrs) |  | 
| 681 |  | 
| 682         self.server.factory.expectedLoseConnection = True |  | 
| 683         cmds = ('-o', 'IdentityFile=dsa_test', |  | 
| 684                 '-o', 'UserKnownHostsFile=kh_test', |  | 
| 685                 '-o', 'HostKeyAlgorithms=ssh-rsa', |  | 
| 686                 '-o', 'Port=%i' % (port,), '-b', fn, 'testuser@127.0.0.1') |  | 
| 687         d = getProcessOutputAndValue("sftp", cmds) |  | 
| 688         def check(result): |  | 
| 689             self.assertEquals(result[2], 0) |  | 
| 690             for i in ['testDirectory', 'testRemoveFile', |  | 
| 691                       'testRenameFile', 'testfile1']: |  | 
| 692                 self.assertIn(i, result[0]) |  | 
| 693         return d.addCallback(check) |  | 
| 694 |  | 
| 695 |  | 
| 696 |  | 
| 697 if not unix or not Crypto or not interfaces.IReactorProcess(reactor, None): |  | 
| 698     TestOurServerCmdLineClient.skip = "don't run w/o spawnprocess or PyCrypto" |  | 
| 699     TestOurServerBatchFile.skip = "don't run w/o spawnProcess or PyCrypto" |  | 
| 700     TestOurServerUnixClient.skip = "don't run w/o spawnProcess or PyCrypto" |  | 
| 701     TestOurServerSftpClient.skip = "don't run w/o spawnProcess or PyCrypto" |  | 
| 702 else: |  | 
| 703     from twisted.python.procutils import which |  | 
| 704     if not which('sftp'): |  | 
| 705         TestOurServerSftpClient.skip = "no sftp command-line client available" |  | 
| OLD | NEW | 
|---|