| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.conch.test.test_conch -*- | |
| 2 # Copyright (c) 2001-2008 Twisted Matrix Laboratories. | |
| 3 # See LICENSE for details. | |
| 4 | |
| 5 import os, sys | |
| 6 | |
| 7 try: | |
| 8 import Crypto | |
| 9 except: | |
| 10 Crypto = None | |
| 11 | |
| 12 from twisted.cred import portal | |
| 13 from twisted.internet import reactor, defer, protocol | |
| 14 from twisted.internet.error import ProcessExitedAlready | |
| 15 from twisted.python import log, runtime | |
| 16 from twisted.python.filepath import FilePath | |
| 17 from twisted.trial import unittest | |
| 18 from twisted.conch.error import ConchError | |
| 19 from twisted.conch.test.test_ssh import ConchTestRealm | |
| 20 from twisted.python.procutils import which | |
| 21 | |
| 22 from twisted.conch.test.keydata import publicRSA_openssh, privateRSA_openssh | |
| 23 from twisted.conch.test.keydata import publicDSA_openssh, privateDSA_openssh | |
| 24 | |
| 25 | |
| 26 | |
| 27 class Echo(protocol.Protocol): | |
| 28 def connectionMade(self): | |
| 29 log.msg('ECHO CONNECTION MADE') | |
| 30 | |
| 31 | |
| 32 def connectionLost(self, reason): | |
| 33 log.msg('ECHO CONNECTION DONE') | |
| 34 | |
| 35 | |
| 36 def dataReceived(self, data): | |
| 37 self.transport.write(data) | |
| 38 if '\n' in data: | |
| 39 self.transport.loseConnection() | |
| 40 | |
| 41 | |
| 42 | |
| 43 class EchoFactory(protocol.Factory): | |
| 44 protocol = Echo | |
| 45 | |
| 46 | |
| 47 | |
| 48 class ConchTestOpenSSHProcess(protocol.ProcessProtocol): | |
| 49 """ | |
| 50 Test protocol for launching an OpenSSH client process. | |
| 51 | |
| 52 @ivar deferred: Set by whatever uses this object. Accessed using | |
| 53 L{_getDeferred}, which destroys the value so the Deferred is not | |
| 54 fired twice. Fires when the process is terminated. | |
| 55 """ | |
| 56 | |
| 57 deferred = None | |
| 58 buf = '' | |
| 59 | |
| 60 def _getDeferred(self): | |
| 61 d, self.deferred = self.deferred, None | |
| 62 return d | |
| 63 | |
| 64 | |
| 65 def outReceived(self, data): | |
| 66 self.buf += data | |
| 67 | |
| 68 | |
| 69 def processEnded(self, reason): | |
| 70 """ | |
| 71 Called when the process has ended. | |
| 72 | |
| 73 @param reason: a Failure giving the reason for the process' end. | |
| 74 """ | |
| 75 if reason.value.exitCode != 0: | |
| 76 self._getDeferred().errback( | |
| 77 ConchError("exit code was not 0: %s" % | |
| 78 reason.value.exitCode)) | |
| 79 else: | |
| 80 buf = self.buf.replace('\r\n', '\n') | |
| 81 self._getDeferred().callback(buf) | |
| 82 | |
| 83 | |
| 84 | |
| 85 class ConchTestForwardingProcess(protocol.ProcessProtocol): | |
| 86 """ | |
| 87 Manages a third-party process which launches a server. | |
| 88 | |
| 89 Uses L{ConchTestForwardingPort} to connect to the third-party server. | |
| 90 Once L{ConchTestForwardingPort} has disconnected, kill the process and fire | |
| 91 a Deferred with the data received by the L{ConchTestForwardingPort}. | |
| 92 | |
| 93 @ivar deferred: Set by whatever uses this object. Accessed using | |
| 94 L{_getDeferred}, which destroys the value so the Deferred is not | |
| 95 fired twice. Fires when the process is terminated. | |
| 96 """ | |
| 97 | |
| 98 deferred = None | |
| 99 | |
| 100 def __init__(self, port, data): | |
| 101 """ | |
| 102 @type port: C{int} | |
| 103 @param port: The port on which the third-party server is listening. | |
| 104 (it is assumed that the server is running on localhost). | |
| 105 | |
| 106 @type data: C{str} | |
| 107 @param data: This is sent to the third-party server. Must end with '\n' | |
| 108 in order to trigger a disconnect. | |
| 109 """ | |
| 110 self.port = port | |
| 111 self.buffer = None | |
| 112 self.data = data | |
| 113 | |
| 114 | |
| 115 def _getDeferred(self): | |
| 116 d, self.deferred = self.deferred, None | |
| 117 return d | |
| 118 | |
| 119 | |
| 120 def connectionMade(self): | |
| 121 self._connect() | |
| 122 | |
| 123 | |
| 124 def _connect(self): | |
| 125 """ | |
| 126 Connect to the server, which is often a third-party process. | |
| 127 Tries to reconnect if it fails because we have no way of determining | |
| 128 exactly when the port becomes available for listening -- we can only | |
| 129 know when the process starts. | |
| 130 """ | |
| 131 cc = protocol.ClientCreator(reactor, ConchTestForwardingPort, self, | |
| 132 self.data) | |
| 133 d = cc.connectTCP('127.0.0.1', self.port) | |
| 134 d.addErrback(self._ebConnect) | |
| 135 return d | |
| 136 | |
| 137 | |
| 138 def _ebConnect(self, f): | |
| 139 reactor.callLater(1, self._connect) | |
| 140 | |
| 141 | |
| 142 def forwardingPortDisconnected(self, buffer): | |
| 143 """ | |
| 144 The network connection has died; save the buffer of output | |
| 145 from the network and attempt to quit the process gracefully, | |
| 146 and then (after the reactor has spun) send it a KILL signal. | |
| 147 """ | |
| 148 self.buffer = buffer | |
| 149 self.transport.write('\x03') | |
| 150 self.transport.loseConnection() | |
| 151 reactor.callLater(0, self._reallyDie) | |
| 152 | |
| 153 | |
| 154 def _reallyDie(self): | |
| 155 try: | |
| 156 self.transport.signalProcess('KILL') | |
| 157 except ProcessExitedAlready: | |
| 158 pass | |
| 159 | |
| 160 | |
| 161 def processEnded(self, reason): | |
| 162 """ | |
| 163 Fire the Deferred at self.deferred with the data collected | |
| 164 from the L{ConchTestForwardingPort} connection, if any. | |
| 165 """ | |
| 166 self._getDeferred().callback(self.buffer) | |
| 167 | |
| 168 | |
| 169 | |
| 170 class ConchTestForwardingPort(protocol.Protocol): | |
| 171 """ | |
| 172 Connects to server launched by a third-party process (managed by | |
| 173 L{ConchTestForwardingProcess}) sends data, then reports whatever it | |
| 174 received back to the L{ConchTestForwardingProcess} once the connection | |
| 175 is ended. | |
| 176 """ | |
| 177 | |
| 178 | |
| 179 def __init__(self, protocol, data): | |
| 180 """ | |
| 181 @type protocol: L{ConchTestForwardingProcess} | |
| 182 @param protocol: The L{ProcessProtocol} which made this connection. | |
| 183 | |
| 184 @type data: str | |
| 185 @param data: The data to be sent to the third-party server. | |
| 186 """ | |
| 187 self.protocol = protocol | |
| 188 self.data = data | |
| 189 | |
| 190 | |
| 191 def connectionMade(self): | |
| 192 self.buffer = '' | |
| 193 self.transport.write(self.data) | |
| 194 | |
| 195 | |
| 196 def dataReceived(self, data): | |
| 197 self.buffer += data | |
| 198 | |
| 199 | |
| 200 def connectionLost(self, reason): | |
| 201 self.protocol.forwardingPortDisconnected(self.buffer) | |
| 202 | |
| 203 | |
| 204 | |
| 205 if Crypto: | |
| 206 from twisted.conch.client import options, default, connect | |
| 207 from twisted.conch.ssh import forwarding | |
| 208 from twisted.conch.ssh import connection | |
| 209 | |
| 210 from twisted.conch.test.test_ssh import ConchTestServerFactory | |
| 211 from twisted.conch.test.test_ssh import ConchTestPublicKeyChecker | |
| 212 | |
| 213 | |
| 214 class SSHTestConnectionForUnix(connection.SSHConnection): | |
| 215 """ | |
| 216 @ivar stopDeferred: Deferred that will be fired when C{serviceStopped} | |
| 217 is called. | |
| 218 @type stopDeferred: C{defer.Deferred} | |
| 219 """ | |
| 220 | |
| 221 def __init__(self, p, exe=None, cmds=None): | |
| 222 connection.SSHConnection.__init__(self) | |
| 223 if p: | |
| 224 self.spawn = (p, exe, cmds) | |
| 225 else: | |
| 226 self.spawn = None | |
| 227 self.connected = 0 | |
| 228 self.remoteForwards = {} | |
| 229 self.stopDeferred = defer.Deferred() | |
| 230 | |
| 231 def serviceStopped(self): | |
| 232 self.stopDeferred.callback(None) | |
| 233 | |
| 234 def serviceStarted(self): | |
| 235 if self.spawn: | |
| 236 env = os.environ.copy() | |
| 237 env['PYTHONPATH'] = os.pathsep.join(sys.path) | |
| 238 reactor.callLater(0,reactor.spawnProcess, env=env, *self.spawn) | |
| 239 self.connected = 1 | |
| 240 | |
| 241 def requestRemoteForwarding(self, remotePort, hostport): | |
| 242 data = forwarding.packGlobal_tcpip_forward(('0.0.0.0', remotePort)) | |
| 243 d = self.sendGlobalRequest('tcpip-forward', data, | |
| 244 wantReply=1) | |
| 245 log.msg('requesting remote forwarding %s:%s' %(remotePort, hostport)
) | |
| 246 d.addCallback(self._cbRemoteForwarding, remotePort, hostport) | |
| 247 d.addErrback(self._ebRemoteForwarding, remotePort, hostport) | |
| 248 | |
| 249 def _cbRemoteForwarding(self, result, remotePort, hostport): | |
| 250 log.msg('accepted remote forwarding %s:%s' % (remotePort, hostport)) | |
| 251 self.remoteForwards[remotePort] = hostport | |
| 252 log.msg(repr(self.remoteForwards)) | |
| 253 | |
| 254 def _ebRemoteForwarding(self, f, remotePort, hostport): | |
| 255 log.msg('remote forwarding %s:%s failed' % (remotePort, hostport)) | |
| 256 log.msg(f) | |
| 257 | |
| 258 def cancelRemoteForwarding(self, remotePort): | |
| 259 data = forwarding.packGlobal_tcpip_forward(('0.0.0.0', remotePort)) | |
| 260 self.sendGlobalRequest('cancel-tcpip-forward', data) | |
| 261 log.msg('cancelling remote forwarding %s' % remotePort) | |
| 262 try: | |
| 263 del self.remoteForwards[remotePort] | |
| 264 except: | |
| 265 pass | |
| 266 log.msg(repr(self.remoteForwards)) | |
| 267 | |
| 268 def channel_forwarded_tcpip(self, windowSize, maxPacket, data): | |
| 269 log.msg('%s %s' % ('FTCP', repr(data))) | |
| 270 remoteHP, origHP = forwarding.unpackOpen_forwarded_tcpip(data) | |
| 271 log.msg(self.remoteForwards) | |
| 272 log.msg(remoteHP) | |
| 273 if self.remoteForwards.has_key(remoteHP[1]): | |
| 274 connectHP = self.remoteForwards[remoteHP[1]] | |
| 275 log.msg('connect forwarding %s' % (connectHP,)) | |
| 276 return forwarding.SSHConnectForwardingChannel(connectHP, | |
| 277 remoteWindow = windowSize, | |
| 278 remoteMaxPacket = maxPacket, | |
| 279 conn = self) | |
| 280 else: | |
| 281 raise ConchError(connection.OPEN_CONNECT_FAILED, "don't know abo
ut that port") | |
| 282 | |
| 283 | |
| 284 | |
| 285 def _makeArgs(args, mod="conch"): | |
| 286 start = [sys.executable, '-c' | |
| 287 """ | |
| 288 ### Twisted Preamble | |
| 289 import sys, os | |
| 290 path = os.path.abspath(sys.argv[0]) | |
| 291 while os.path.dirname(path) != path: | |
| 292 if os.path.basename(path).startswith('Twisted'): | |
| 293 sys.path.insert(0, path) | |
| 294 break | |
| 295 path = os.path.dirname(path) | |
| 296 | |
| 297 from twisted.conch.scripts.%s import run | |
| 298 run()""" % mod] | |
| 299 return start + list(args) | |
| 300 | |
| 301 | |
| 302 | |
| 303 class ForwardingTestBase: | |
| 304 """ | |
| 305 Template class for tests of the Conch server's ability to forward arbitrary | |
| 306 protocols over SSH. | |
| 307 | |
| 308 These tests are integration tests, not unit tests. They launch a Conch | |
| 309 server, a custom TCP server (just an L{EchoProtocol}) and then call | |
| 310 L{execute}. | |
| 311 | |
| 312 L{execute} is implemented by subclasses of L{ForwardingTestBase}. It should | |
| 313 cause an SSH client to connect to the Conch server, asking it to forward | |
| 314 data to the custom TCP server. | |
| 315 """ | |
| 316 | |
| 317 if not Crypto: | |
| 318 skip = "can't run w/o PyCrypto" | |
| 319 | |
| 320 def _createFiles(self): | |
| 321 for f in ['rsa_test','rsa_test.pub','dsa_test','dsa_test.pub', | |
| 322 'kh_test']: | |
| 323 if os.path.exists(f): | |
| 324 os.remove(f) | |
| 325 open('rsa_test','w').write(privateRSA_openssh) | |
| 326 open('rsa_test.pub','w').write(publicRSA_openssh) | |
| 327 open('dsa_test.pub','w').write(publicDSA_openssh) | |
| 328 open('dsa_test','w').write(privateDSA_openssh) | |
| 329 os.chmod('dsa_test', 33152) | |
| 330 os.chmod('rsa_test', 33152) | |
| 331 open('kh_test','w').write('127.0.0.1 '+publicRSA_openssh) | |
| 332 | |
| 333 | |
| 334 def _getFreePort(self): | |
| 335 f = EchoFactory() | |
| 336 serv = reactor.listenTCP(0, f) | |
| 337 port = serv.getHost().port | |
| 338 serv.stopListening() | |
| 339 return port | |
| 340 | |
| 341 | |
| 342 def _makeConchFactory(self): | |
| 343 """ | |
| 344 Make a L{ConchTestServerFactory}, which allows us to start a | |
| 345 L{ConchTestServer} -- i.e. an actually listening conch. | |
| 346 """ | |
| 347 realm = ConchTestRealm() | |
| 348 p = portal.Portal(realm) | |
| 349 p.registerChecker(ConchTestPublicKeyChecker()) | |
| 350 factory = ConchTestServerFactory() | |
| 351 factory.portal = p | |
| 352 return factory | |
| 353 | |
| 354 | |
| 355 def setUp(self): | |
| 356 self._createFiles() | |
| 357 self.conchFactory = self._makeConchFactory() | |
| 358 self.conchFactory.expectedLoseConnection = 1 | |
| 359 self.conchServer = reactor.listenTCP(0, self.conchFactory, | |
| 360 interface="127.0.0.1") | |
| 361 self.echoServer = reactor.listenTCP(0, EchoFactory()) | |
| 362 self.echoPort = self.echoServer.getHost().port | |
| 363 | |
| 364 | |
| 365 def tearDown(self): | |
| 366 try: | |
| 367 self.conchFactory.proto.done = 1 | |
| 368 except AttributeError: | |
| 369 pass | |
| 370 else: | |
| 371 self.conchFactory.proto.transport.loseConnection() | |
| 372 return defer.gatherResults([ | |
| 373 defer.maybeDeferred(self.conchServer.stopListening), | |
| 374 defer.maybeDeferred(self.echoServer.stopListening)]) | |
| 375 | |
| 376 | |
| 377 def test_exec(self): | |
| 378 """ | |
| 379 Test that we can use whatever client to send the command "echo goodbye" | |
| 380 to the Conch server. Make sure we receive "goodbye" back from the | |
| 381 server. | |
| 382 """ | |
| 383 d = self.execute('echo goodbye', ConchTestOpenSSHProcess()) | |
| 384 return d.addCallback(self.assertEquals, 'goodbye\n') | |
| 385 | |
| 386 | |
| 387 def test_localToRemoteForwarding(self): | |
| 388 """ | |
| 389 Test that we can use whatever client to forward a local port to a | |
| 390 specified port on the server. | |
| 391 """ | |
| 392 lport = self._getFreePort() | |
| 393 process = ConchTestForwardingProcess(lport, 'test\n') | |
| 394 d = self.execute('', process, | |
| 395 sshArgs='-N -L%i:127.0.0.1:%i' | |
| 396 % (lport, self.echoPort)) | |
| 397 d.addCallback(self.assertEqual, 'test\n') | |
| 398 return d | |
| 399 | |
| 400 | |
| 401 def test_remoteToLocalForwarding(self): | |
| 402 """ | |
| 403 Test that we can use whatever client to forward a port from the server | |
| 404 to a port locally. | |
| 405 """ | |
| 406 localPort = self._getFreePort() | |
| 407 process = ConchTestForwardingProcess(localPort, 'test\n') | |
| 408 d = self.execute('', process, | |
| 409 sshArgs='-N -R %i:127.0.0.1:%i' | |
| 410 % (localPort, self.echoPort)) | |
| 411 d.addCallback(self.assertEqual, 'test\n') | |
| 412 return d | |
| 413 | |
| 414 | |
| 415 | |
| 416 class OpenSSHClientTestCase(ForwardingTestBase, unittest.TestCase): | |
| 417 | |
| 418 def execute(self, remoteCommand, process, sshArgs=''): | |
| 419 """ | |
| 420 Connects to the SSH server started in L{ForwardingTestBase.setUp} by | |
| 421 running the 'ssh' command line tool. | |
| 422 | |
| 423 @type remoteCommand: str | |
| 424 @param remoteCommand: The command (with arguments) to run on the | |
| 425 remote end. | |
| 426 | |
| 427 @type process: L{ConchTestOpenSSHProcess} | |
| 428 | |
| 429 @type sshArgs: str | |
| 430 @param sshArgs: Arguments to pass to the 'ssh' process. | |
| 431 | |
| 432 @return: L{defer.Deferred} | |
| 433 """ | |
| 434 process.deferred = defer.Deferred() | |
| 435 cmdline = ('ssh -2 -l testuser -p %i ' | |
| 436 '-oUserKnownHostsFile=kh_test ' | |
| 437 '-oPasswordAuthentication=no ' | |
| 438 # Always use the RSA key, since that's the one in kh_test. | |
| 439 '-oHostKeyAlgorithms=ssh-rsa ' | |
| 440 '-a ' | |
| 441 '-i dsa_test ') + sshArgs + \ | |
| 442 ' 127.0.0.1 ' + remoteCommand | |
| 443 port = self.conchServer.getHost().port | |
| 444 cmds = (cmdline % port).split() | |
| 445 reactor.spawnProcess(process, "ssh", cmds) | |
| 446 return process.deferred | |
| 447 | |
| 448 | |
| 449 | |
| 450 class CmdLineClientTestCase(ForwardingTestBase, unittest.TestCase): | |
| 451 def setUp(self): | |
| 452 if runtime.platformType == 'win32': | |
| 453 raise unittest.SkipTest("can't run cmdline client on win32") | |
| 454 ForwardingTestBase.setUp(self) | |
| 455 | |
| 456 | |
| 457 def execute(self, remoteCommand, process, sshArgs=''): | |
| 458 """ | |
| 459 As for L{OpenSSHClientTestCase.execute}, except it runs the 'conch' | |
| 460 command line tool, not 'ssh'. | |
| 461 """ | |
| 462 process.deferred = defer.Deferred() | |
| 463 port = self.conchServer.getHost().port | |
| 464 cmd = ('-p %i -l testuser ' | |
| 465 '--known-hosts kh_test ' | |
| 466 '--user-authentications publickey ' | |
| 467 '--host-key-algorithms ssh-rsa ' | |
| 468 '-a -I ' | |
| 469 '-K direct ' | |
| 470 '-i dsa_test ' | |
| 471 '-v ') % port + sshArgs + \ | |
| 472 ' 127.0.0.1 ' + remoteCommand | |
| 473 cmds = _makeArgs(cmd.split()) | |
| 474 log.msg(str(cmds)) | |
| 475 env = os.environ.copy() | |
| 476 env['PYTHONPATH'] = os.pathsep.join(sys.path) | |
| 477 reactor.spawnProcess(process, sys.executable, cmds, env=env) | |
| 478 return process.deferred | |
| 479 | |
| 480 | |
| 481 | |
| 482 class _UnixFixHome(object): | |
| 483 """ | |
| 484 Mixin class to fix the HOME environment variable to something usable. | |
| 485 | |
| 486 @ivar home: FilePath pointing at C{homePath}. | |
| 487 @type home: L{FilePath} | |
| 488 | |
| 489 @ivar homePath: relative path to the directory used as HOME during the | |
| 490 tests. | |
| 491 @type homePath: C{str} | |
| 492 """ | |
| 493 | |
| 494 def setUp(self): | |
| 495 path = self.mktemp() | |
| 496 self.home = FilePath(path) | |
| 497 self.homePath = os.path.join(*self.home.segmentsFrom(FilePath("."))) | |
| 498 if len(self.home.path) >= 70: | |
| 499 # UNIX_MAX_PATH is 108, and the socket file is generally of length | |
| 500 # 30, so we can't rely on mktemp... | |
| 501 self.homePath = "_tmp" | |
| 502 self.home = FilePath(self.homePath) | |
| 503 self.home.makedirs() | |
| 504 self.savedEnviron = os.environ.copy() | |
| 505 os.environ["HOME"] = self.homePath | |
| 506 | |
| 507 | |
| 508 def tearDown(self): | |
| 509 os.environ.clear() | |
| 510 os.environ.update(self.savedEnviron) | |
| 511 self.home.remove() | |
| 512 | |
| 513 | |
| 514 | |
| 515 class UnixClientTestCase(_UnixFixHome, ForwardingTestBase, unittest.TestCase): | |
| 516 def setUp(self): | |
| 517 if runtime.platformType == 'win32': | |
| 518 raise unittest.SkipTest("can't run cmdline client on win32") | |
| 519 ForwardingTestBase.setUp(self) | |
| 520 _UnixFixHome.setUp(self) | |
| 521 | |
| 522 | |
| 523 def tearDown(self): | |
| 524 d1 = ForwardingTestBase.tearDown(self) | |
| 525 d2 = defer.maybeDeferred(self.conn.transport.transport.loseConnection) | |
| 526 d3 = self.conn.stopDeferred | |
| 527 def clean(ign): | |
| 528 _UnixFixHome.tearDown(self) | |
| 529 return ign | |
| 530 return defer.gatherResults([d1, d2, d3]).addBoth(clean) | |
| 531 | |
| 532 | |
| 533 def makeOptions(self): | |
| 534 o = options.ConchOptions() | |
| 535 def parseArgs(host, *args): | |
| 536 o['host'] = host | |
| 537 o.parseArgs = parseArgs | |
| 538 return o | |
| 539 | |
| 540 | |
| 541 def makeAuthClient(self, port, options): | |
| 542 cmds = (('-p %i -l testuser ' | |
| 543 '--known-hosts kh_test ' | |
| 544 '--user-authentications publickey ' | |
| 545 '--host-key-algorithms ssh-rsa ' | |
| 546 '-a ' | |
| 547 '-K direct ' | |
| 548 '-i dsa_test ' | |
| 549 '127.0.0.1') % port).split() | |
| 550 options.parseOptions(cmds) | |
| 551 return default.SSHUserAuthClient(options['user'], options, self.conn) | |
| 552 | |
| 553 | |
| 554 def execute(self, remoteCommand, process, sshArgs=''): | |
| 555 """ | |
| 556 Connect to the forwarding process using the 'unix' client found in | |
| 557 L{twisted.conch.client.unix.connect}. See | |
| 558 L{OpenSSHClientTestCase.execute}. | |
| 559 """ | |
| 560 process.deferred = defer.Deferred() | |
| 561 port = self.conchServer.getHost().port | |
| 562 cmd = ('-p %i -l testuser ' | |
| 563 '-K unix ' | |
| 564 '-v ') % port + sshArgs + \ | |
| 565 ' 127.0.0.1 ' + remoteCommand | |
| 566 cmds = _makeArgs(cmd.split()) | |
| 567 options = self.makeOptions() | |
| 568 self.conn = SSHTestConnectionForUnix(process, sys.executable, cmds) | |
| 569 authClient = self.makeAuthClient(port, options) | |
| 570 d = connect.connect(options['host'], port, options, | |
| 571 default.verifyHostKey, authClient) | |
| 572 return d.addCallback(lambda x : process.deferred) | |
| 573 | |
| 574 | |
| 575 def test_noHome(self): | |
| 576 """ | |
| 577 When setting the HOME environment variable to a path that doesn't | |
| 578 exist, L{connect.connect} should forward the failure, and the created | |
| 579 process should fail with a L{ConchError}. | |
| 580 """ | |
| 581 path = self.mktemp() | |
| 582 # We override the HOME variable, and let tearDown restore the initial | |
| 583 # value | |
| 584 os.environ['HOME'] = path | |
| 585 process = ConchTestOpenSSHProcess() | |
| 586 d = self.execute('echo goodbye', process) | |
| 587 def cb(ign): | |
| 588 return self.assertFailure(process.deferred, ConchError) | |
| 589 return self.assertFailure(d, OSError).addCallback(cb) | |
| 590 | |
| 591 | |
| 592 | |
| 593 if not which('ssh'): | |
| 594 OpenSSHClientTestCase.skip = "no ssh command-line client available" | |
| OLD | NEW |