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 |