| 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 |