endpoints.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  1. # -*- test-case-name: twisted.conch.test.test_endpoints -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Endpoint implementations of various SSH interactions.
  6. """
  7. __all__ = [
  8. 'AuthenticationFailed', 'SSHCommandAddress', 'SSHCommandClientEndpoint']
  9. from struct import unpack
  10. from os.path import expanduser
  11. import signal
  12. from zope.interface import Interface, implementer
  13. from twisted.logger import Logger
  14. from twisted.python.compat import nativeString, networkString
  15. from twisted.python.filepath import FilePath
  16. from twisted.python.failure import Failure
  17. from twisted.internet.error import ConnectionDone, ProcessTerminated
  18. from twisted.internet.interfaces import IStreamClientEndpoint
  19. from twisted.internet.protocol import Factory
  20. from twisted.internet.defer import Deferred, succeed, CancelledError
  21. from twisted.internet.endpoints import TCP4ClientEndpoint, connectProtocol
  22. from twisted.conch.ssh.keys import Key
  23. from twisted.conch.ssh.common import getNS, NS
  24. from twisted.conch.ssh.transport import SSHClientTransport
  25. from twisted.conch.ssh.connection import SSHConnection
  26. from twisted.conch.ssh.userauth import SSHUserAuthClient
  27. from twisted.conch.ssh.channel import SSHChannel
  28. from twisted.conch.client.knownhosts import ConsoleUI, KnownHostsFile
  29. from twisted.conch.client.agent import SSHAgentClient
  30. from twisted.conch.client.default import _KNOWN_HOSTS
  31. class AuthenticationFailed(Exception):
  32. """
  33. An SSH session could not be established because authentication was not
  34. successful.
  35. """
  36. # This should be public. See #6541.
  37. class _ISSHConnectionCreator(Interface):
  38. """
  39. An L{_ISSHConnectionCreator} knows how to create SSH connections somehow.
  40. """
  41. def secureConnection():
  42. """
  43. Return a new, connected, secured, but not yet authenticated instance of
  44. L{twisted.conch.ssh.transport.SSHServerTransport} or
  45. L{twisted.conch.ssh.transport.SSHClientTransport}.
  46. """
  47. def cleanupConnection(connection, immediate):
  48. """
  49. Perform cleanup necessary for a connection object previously returned
  50. from this creator's C{secureConnection} method.
  51. @param connection: An L{twisted.conch.ssh.transport.SSHServerTransport}
  52. or L{twisted.conch.ssh.transport.SSHClientTransport} returned by a
  53. previous call to C{secureConnection}. It is no longer needed by
  54. the caller of that method and may be closed or otherwise cleaned up
  55. as necessary.
  56. @param immediate: If C{True} don't wait for any network communication,
  57. just close the connection immediately and as aggressively as
  58. necessary.
  59. """
  60. class SSHCommandAddress(object):
  61. """
  62. An L{SSHCommandAddress} instance represents the address of an SSH server, a
  63. username which was used to authenticate with that server, and a command
  64. which was run there.
  65. @ivar server: See L{__init__}
  66. @ivar username: See L{__init__}
  67. @ivar command: See L{__init__}
  68. """
  69. def __init__(self, server, username, command):
  70. """
  71. @param server: The address of the SSH server on which the command is
  72. running.
  73. @type server: L{IAddress} provider
  74. @param username: An authentication username which was used to
  75. authenticate against the server at the given address.
  76. @type username: L{bytes}
  77. @param command: A command which was run in a session channel on the
  78. server at the given address.
  79. @type command: L{bytes}
  80. """
  81. self.server = server
  82. self.username = username
  83. self.command = command
  84. class _CommandChannel(SSHChannel):
  85. """
  86. A L{_CommandChannel} executes a command in a session channel and connects
  87. its input and output to an L{IProtocol} provider.
  88. @ivar _creator: See L{__init__}
  89. @ivar _command: See L{__init__}
  90. @ivar _protocolFactory: See L{__init__}
  91. @ivar _commandConnected: See L{__init__}
  92. @ivar _protocol: An L{IProtocol} provider created using C{_protocolFactory}
  93. which is hooked up to the running command's input and output streams.
  94. """
  95. name = b'session'
  96. _log = Logger()
  97. def __init__(self, creator, command, protocolFactory, commandConnected):
  98. """
  99. @param creator: The L{_ISSHConnectionCreator} provider which was used
  100. to get the connection which this channel exists on.
  101. @type creator: L{_ISSHConnectionCreator} provider
  102. @param command: The command to be executed.
  103. @type command: L{bytes}
  104. @param protocolFactory: A client factory to use to build a L{IProtocol}
  105. provider to use to associate with the running command.
  106. @param commandConnected: A L{Deferred} to use to signal that execution
  107. of the command has failed or that it has succeeded and the command
  108. is now running.
  109. @type commandConnected: L{Deferred}
  110. """
  111. SSHChannel.__init__(self)
  112. self._creator = creator
  113. self._command = command
  114. self._protocolFactory = protocolFactory
  115. self._commandConnected = commandConnected
  116. self._reason = None
  117. def openFailed(self, reason):
  118. """
  119. When the request to open a new channel to run this command in fails,
  120. fire the C{commandConnected} deferred with a failure indicating that.
  121. """
  122. self._commandConnected.errback(reason)
  123. def channelOpen(self, ignored):
  124. """
  125. When the request to open a new channel to run this command in succeeds,
  126. issue an C{"exec"} request to run the command.
  127. """
  128. command = self.conn.sendRequest(
  129. self, b'exec', NS(self._command), wantReply=True)
  130. command.addCallbacks(self._execSuccess, self._execFailure)
  131. def _execFailure(self, reason):
  132. """
  133. When the request to execute the command in this channel fails, fire the
  134. C{commandConnected} deferred with a failure indicating this.
  135. @param reason: The cause of the command execution failure.
  136. @type reason: L{Failure}
  137. """
  138. self._commandConnected.errback(reason)
  139. def _execSuccess(self, ignored):
  140. """
  141. When the request to execute the command in this channel succeeds, use
  142. C{protocolFactory} to build a protocol to handle the command's input
  143. and output and connect the protocol to a transport representing those
  144. streams.
  145. Also fire C{commandConnected} with the created protocol after it is
  146. connected to its transport.
  147. @param ignored: The (ignored) result of the execute request
  148. """
  149. self._protocol = self._protocolFactory.buildProtocol(
  150. SSHCommandAddress(
  151. self.conn.transport.transport.getPeer(),
  152. self.conn.transport.creator.username,
  153. self.conn.transport.creator.command))
  154. self._protocol.makeConnection(self)
  155. self._commandConnected.callback(self._protocol)
  156. def dataReceived(self, data):
  157. """
  158. When the command's stdout data arrives over the channel, deliver it to
  159. the protocol instance.
  160. @param data: The bytes from the command's stdout.
  161. @type data: L{bytes}
  162. """
  163. self._protocol.dataReceived(data)
  164. def request_exit_status(self, data):
  165. """
  166. When the server sends the command's exit status, record it for later
  167. delivery to the protocol.
  168. @param data: The network-order four byte representation of the exit
  169. status of the command.
  170. @type data: L{bytes}
  171. """
  172. (status,) = unpack('>L', data)
  173. if status != 0:
  174. self._reason = ProcessTerminated(status, None, None)
  175. def request_exit_signal(self, data):
  176. """
  177. When the server sends the command's exit status, record it for later
  178. delivery to the protocol.
  179. @param data: The network-order four byte representation of the exit
  180. signal of the command.
  181. @type data: L{bytes}
  182. """
  183. shortSignalName, data = getNS(data)
  184. coreDumped, data = bool(ord(data[0:1])), data[1:]
  185. errorMessage, data = getNS(data)
  186. languageTag, data = getNS(data)
  187. signalName = "SIG%s" % (nativeString(shortSignalName),)
  188. signalID = getattr(signal, signalName, -1)
  189. self._log.info(
  190. "Process exited with signal {shortSignalName!r};"
  191. " core dumped: {coreDumped};"
  192. " error message: {errorMessage};"
  193. " language: {languageTag!r}",
  194. shortSignalName=shortSignalName,
  195. coreDumped=coreDumped,
  196. errorMessage=errorMessage.decode('utf-8'),
  197. languageTag=languageTag,
  198. )
  199. self._reason = ProcessTerminated(None, signalID, None)
  200. def closed(self):
  201. """
  202. When the channel closes, deliver disconnection notification to the
  203. protocol.
  204. """
  205. self._creator.cleanupConnection(self.conn, False)
  206. if self._reason is None:
  207. reason = ConnectionDone("ssh channel closed")
  208. else:
  209. reason = self._reason
  210. self._protocol.connectionLost(Failure(reason))
  211. class _ConnectionReady(SSHConnection):
  212. """
  213. L{_ConnectionReady} is an L{SSHConnection} (an SSH service) which only
  214. propagates the I{serviceStarted} event to a L{Deferred} to be handled
  215. elsewhere.
  216. """
  217. def __init__(self, ready):
  218. """
  219. @param ready: A L{Deferred} which should be fired when
  220. I{serviceStarted} happens.
  221. """
  222. SSHConnection.__init__(self)
  223. self._ready = ready
  224. def serviceStarted(self):
  225. """
  226. When the SSH I{connection} I{service} this object represents is ready
  227. to be used, fire the C{connectionReady} L{Deferred} to publish that
  228. event to some other interested party.
  229. """
  230. self._ready.callback(self)
  231. del self._ready
  232. class _UserAuth(SSHUserAuthClient):
  233. """
  234. L{_UserAuth} implements the client part of SSH user authentication in the
  235. convenient way a user might expect if they are familiar with the
  236. interactive I{ssh} command line client.
  237. L{_UserAuth} supports key-based authentication, password-based
  238. authentication, and delegating authentication to an agent.
  239. """
  240. password = None
  241. keys = None
  242. agent = None
  243. def getPublicKey(self):
  244. """
  245. Retrieve the next public key object to offer to the server, possibly
  246. delegating to an authentication agent if there is one.
  247. @return: The public part of a key pair that could be used to
  248. authenticate with the server, or L{None} if there are no more
  249. public keys to try.
  250. @rtype: L{twisted.conch.ssh.keys.Key} or L{None}
  251. """
  252. if self.agent is not None:
  253. return self.agent.getPublicKey()
  254. if self.keys:
  255. self.key = self.keys.pop(0)
  256. else:
  257. self.key = None
  258. return self.key.public()
  259. def signData(self, publicKey, signData):
  260. """
  261. Extend the base signing behavior by using an SSH agent to sign the
  262. data, if one is available.
  263. @type publicKey: L{Key}
  264. @type signData: L{str}
  265. """
  266. if self.agent is not None:
  267. return self.agent.signData(publicKey.blob(), signData)
  268. else:
  269. return SSHUserAuthClient.signData(self, publicKey, signData)
  270. def getPrivateKey(self):
  271. """
  272. Get the private part of a key pair to use for authentication. The key
  273. corresponds to the public part most recently returned from
  274. C{getPublicKey}.
  275. @return: A L{Deferred} which fires with the private key.
  276. @rtype: L{Deferred}
  277. """
  278. return succeed(self.key)
  279. def getPassword(self):
  280. """
  281. Get the password to use for authentication.
  282. @return: A L{Deferred} which fires with the password, or L{None} if the
  283. password was not specified.
  284. """
  285. if self.password is None:
  286. return
  287. return succeed(self.password)
  288. def ssh_USERAUTH_SUCCESS(self, packet):
  289. """
  290. Handle user authentication success in the normal way, but also make a
  291. note of the state change on the L{_CommandTransport}.
  292. """
  293. self.transport._state = b'CHANNELLING'
  294. return SSHUserAuthClient.ssh_USERAUTH_SUCCESS(self, packet)
  295. def connectToAgent(self, endpoint):
  296. """
  297. Set up a connection to the authentication agent and trigger its
  298. initialization.
  299. @param endpoint: An endpoint which can be used to connect to the
  300. authentication agent.
  301. @type endpoint: L{IStreamClientEndpoint} provider
  302. @return: A L{Deferred} which fires when the agent connection is ready
  303. for use.
  304. """
  305. factory = Factory()
  306. factory.protocol = SSHAgentClient
  307. d = endpoint.connect(factory)
  308. def connected(agent):
  309. self.agent = agent
  310. return agent.getPublicKeys()
  311. d.addCallback(connected)
  312. return d
  313. def loseAgentConnection(self):
  314. """
  315. Disconnect the agent.
  316. """
  317. if self.agent is None:
  318. return
  319. self.agent.transport.loseConnection()
  320. class _CommandTransport(SSHClientTransport):
  321. """
  322. L{_CommandTransport} is an SSH client I{transport} which includes a host
  323. key verification step before it will proceed to secure the connection.
  324. L{_CommandTransport} also knows how to set up a connection to an
  325. authentication agent if it is told where it can connect to one.
  326. @ivar _userauth: The L{_UserAuth} instance which is in charge of the
  327. overall authentication process or L{None} if the SSH connection has not
  328. reach yet the C{user-auth} service.
  329. @type _userauth: L{_UserAuth}
  330. """
  331. # STARTING -> SECURING -> AUTHENTICATING -> CHANNELLING -> RUNNING
  332. _state = b'STARTING'
  333. _hostKeyFailure = None
  334. _userauth = None
  335. def __init__(self, creator):
  336. """
  337. @param creator: The L{_NewConnectionHelper} that created this
  338. connection.
  339. @type creator: L{_NewConnectionHelper}.
  340. """
  341. self.connectionReady = Deferred(
  342. lambda d: self.transport.abortConnection())
  343. # Clear the reference to that deferred to help the garbage collector
  344. # and to signal to other parts of this implementation (in particular
  345. # connectionLost) that it has already been fired and does not need to
  346. # be fired again.
  347. def readyFired(result):
  348. self.connectionReady = None
  349. return result
  350. self.connectionReady.addBoth(readyFired)
  351. self.creator = creator
  352. def verifyHostKey(self, hostKey, fingerprint):
  353. """
  354. Ask the L{KnownHostsFile} provider available on the factory which
  355. created this protocol this protocol to verify the given host key.
  356. @return: A L{Deferred} which fires with the result of
  357. L{KnownHostsFile.verifyHostKey}.
  358. """
  359. hostname = self.creator.hostname
  360. ip = networkString(self.transport.getPeer().host)
  361. self._state = b'SECURING'
  362. d = self.creator.knownHosts.verifyHostKey(
  363. self.creator.ui, hostname, ip, Key.fromString(hostKey))
  364. d.addErrback(self._saveHostKeyFailure)
  365. return d
  366. def _saveHostKeyFailure(self, reason):
  367. """
  368. When host key verification fails, record the reason for the failure in
  369. order to fire a L{Deferred} with it later.
  370. @param reason: The cause of the host key verification failure.
  371. @type reason: L{Failure}
  372. @return: C{reason}
  373. @rtype: L{Failure}
  374. """
  375. self._hostKeyFailure = reason
  376. return reason
  377. def connectionSecure(self):
  378. """
  379. When the connection is secure, start the authentication process.
  380. """
  381. self._state = b'AUTHENTICATING'
  382. command = _ConnectionReady(self.connectionReady)
  383. self._userauth = _UserAuth(self.creator.username, command)
  384. self._userauth.password = self.creator.password
  385. if self.creator.keys:
  386. self._userauth.keys = list(self.creator.keys)
  387. if self.creator.agentEndpoint is not None:
  388. d = self._userauth.connectToAgent(self.creator.agentEndpoint)
  389. else:
  390. d = succeed(None)
  391. def maybeGotAgent(ignored):
  392. self.requestService(self._userauth)
  393. d.addBoth(maybeGotAgent)
  394. def connectionLost(self, reason):
  395. """
  396. When the underlying connection to the SSH server is lost, if there were
  397. any connection setup errors, propagate them. Also, clean up the
  398. connection to the ssh agent if one was created.
  399. """
  400. if self._userauth:
  401. self._userauth.loseAgentConnection()
  402. if self._state == b'RUNNING' or self.connectionReady is None:
  403. return
  404. if self._state == b'SECURING' and self._hostKeyFailure is not None:
  405. reason = self._hostKeyFailure
  406. elif self._state == b'AUTHENTICATING':
  407. reason = Failure(
  408. AuthenticationFailed("Connection lost while authenticating"))
  409. self.connectionReady.errback(reason)
  410. @implementer(IStreamClientEndpoint)
  411. class SSHCommandClientEndpoint(object):
  412. """
  413. L{SSHCommandClientEndpoint} exposes the command-executing functionality of
  414. SSH servers.
  415. L{SSHCommandClientEndpoint} can set up a new SSH connection, authenticate
  416. it in any one of a number of different ways (keys, passwords, agents),
  417. launch a command over that connection and then associate its input and
  418. output with a protocol.
  419. It can also re-use an existing, already-authenticated SSH connection
  420. (perhaps one which already has some SSH channels being used for other
  421. purposes). In this case it creates a new SSH channel to use to execute the
  422. command. Notably this means it supports multiplexing several different
  423. command invocations over a single SSH connection.
  424. """
  425. def __init__(self, creator, command):
  426. """
  427. @param creator: An L{_ISSHConnectionCreator} provider which will be
  428. used to set up the SSH connection which will be used to run a
  429. command.
  430. @type creator: L{_ISSHConnectionCreator} provider
  431. @param command: The command line to execute on the SSH server. This
  432. byte string is interpreted by a shell on the SSH server, so it may
  433. have a value like C{"ls /"}. Take care when trying to run a
  434. command like C{"/Volumes/My Stuff/a-program"} - spaces (and other
  435. special bytes) may require escaping.
  436. @type command: L{bytes}
  437. """
  438. self._creator = creator
  439. self._command = command
  440. @classmethod
  441. def newConnection(cls, reactor, command, username, hostname, port=None,
  442. keys=None, password=None, agentEndpoint=None,
  443. knownHosts=None, ui=None):
  444. """
  445. Create and return a new endpoint which will try to create a new
  446. connection to an SSH server and run a command over it. It will also
  447. close the connection if there are problems leading up to the command
  448. being executed, after the command finishes, or if the connection
  449. L{Deferred} is cancelled.
  450. @param reactor: The reactor to use to establish the connection.
  451. @type reactor: L{IReactorTCP} provider
  452. @param command: See L{__init__}'s C{command} argument.
  453. @param username: The username with which to authenticate to the SSH
  454. server.
  455. @type username: L{bytes}
  456. @param hostname: The hostname of the SSH server.
  457. @type hostname: L{bytes}
  458. @param port: The port number of the SSH server. By default, the
  459. standard SSH port number is used.
  460. @type port: L{int}
  461. @param keys: Private keys with which to authenticate to the SSH server,
  462. if key authentication is to be attempted (otherwise L{None}).
  463. @type keys: L{list} of L{Key}
  464. @param password: The password with which to authenticate to the SSH
  465. server, if password authentication is to be attempted (otherwise
  466. L{None}).
  467. @type password: L{bytes} or L{None}
  468. @param agentEndpoint: An L{IStreamClientEndpoint} provider which may be
  469. used to connect to an SSH agent, if one is to be used to help with
  470. authentication.
  471. @type agentEndpoint: L{IStreamClientEndpoint} provider
  472. @param knownHosts: The currently known host keys, used to check the
  473. host key presented by the server we actually connect to.
  474. @type knownHosts: L{KnownHostsFile}
  475. @param ui: An object for interacting with users to make decisions about
  476. whether to accept the server host keys. If L{None}, a L{ConsoleUI}
  477. connected to /dev/tty will be used; if /dev/tty is unavailable, an
  478. object which answers C{b"no"} to all prompts will be used.
  479. @type ui: L{None} or L{ConsoleUI}
  480. @return: A new instance of C{cls} (probably
  481. L{SSHCommandClientEndpoint}).
  482. """
  483. helper = _NewConnectionHelper(
  484. reactor, hostname, port, command, username, keys, password,
  485. agentEndpoint, knownHosts, ui)
  486. return cls(helper, command)
  487. @classmethod
  488. def existingConnection(cls, connection, command):
  489. """
  490. Create and return a new endpoint which will try to open a new channel
  491. on an existing SSH connection and run a command over it. It will
  492. B{not} close the connection if there is a problem executing the command
  493. or after the command finishes.
  494. @param connection: An existing connection to an SSH server.
  495. @type connection: L{SSHConnection}
  496. @param command: See L{SSHCommandClientEndpoint.newConnection}'s
  497. C{command} parameter.
  498. @type command: L{bytes}
  499. @return: A new instance of C{cls} (probably
  500. L{SSHCommandClientEndpoint}).
  501. """
  502. helper = _ExistingConnectionHelper(connection)
  503. return cls(helper, command)
  504. def connect(self, protocolFactory):
  505. """
  506. Set up an SSH connection, use a channel from that connection to launch
  507. a command, and hook the stdin and stdout of that command up as a
  508. transport for a protocol created by the given factory.
  509. @param protocolFactory: A L{Factory} to use to create the protocol
  510. which will be connected to the stdin and stdout of the command on
  511. the SSH server.
  512. @return: A L{Deferred} which will fire with an error if the connection
  513. cannot be set up for any reason or with the protocol instance
  514. created by C{protocolFactory} once it has been connected to the
  515. command.
  516. """
  517. d = self._creator.secureConnection()
  518. d.addCallback(self._executeCommand, protocolFactory)
  519. return d
  520. def _executeCommand(self, connection, protocolFactory):
  521. """
  522. Given a secured SSH connection, try to execute a command in a new
  523. channel created on it and associate the result with a protocol from the
  524. given factory.
  525. @param connection: See L{SSHCommandClientEndpoint.existingConnection}'s
  526. C{connection} parameter.
  527. @param protocolFactory: See L{SSHCommandClientEndpoint.connect}'s
  528. C{protocolFactory} parameter.
  529. @return: See L{SSHCommandClientEndpoint.connect}'s return value.
  530. """
  531. commandConnected = Deferred()
  532. def disconnectOnFailure(passthrough):
  533. # Close the connection immediately in case of cancellation, since
  534. # that implies user wants it gone immediately (e.g. a timeout):
  535. immediate = passthrough.check(CancelledError)
  536. self._creator.cleanupConnection(connection, immediate)
  537. return passthrough
  538. commandConnected.addErrback(disconnectOnFailure)
  539. channel = _CommandChannel(
  540. self._creator, self._command, protocolFactory, commandConnected)
  541. connection.openChannel(channel)
  542. return commandConnected
  543. class _ReadFile(object):
  544. """
  545. A weakly file-like object which can be used with L{KnownHostsFile} to
  546. respond in the negative to all prompts for decisions.
  547. """
  548. def __init__(self, contents):
  549. """
  550. @param contents: L{bytes} which will be returned from every C{readline}
  551. call.
  552. """
  553. self._contents = contents
  554. def write(self, data):
  555. """
  556. No-op.
  557. @param data: ignored
  558. """
  559. def readline(self, count=-1):
  560. """
  561. Always give back the byte string that this L{_ReadFile} was initialized
  562. with.
  563. @param count: ignored
  564. @return: A fixed byte-string.
  565. @rtype: L{bytes}
  566. """
  567. return self._contents
  568. def close(self):
  569. """
  570. No-op.
  571. """
  572. @implementer(_ISSHConnectionCreator)
  573. class _NewConnectionHelper(object):
  574. """
  575. L{_NewConnectionHelper} implements L{_ISSHConnectionCreator} by
  576. establishing a brand new SSH connection, securing it, and authenticating.
  577. """
  578. _KNOWN_HOSTS = _KNOWN_HOSTS
  579. port = 22
  580. def __init__(self, reactor, hostname, port, command, username, keys,
  581. password, agentEndpoint, knownHosts, ui,
  582. tty=FilePath(b"/dev/tty")):
  583. """
  584. @param tty: The path of the tty device to use in case C{ui} is L{None}.
  585. @type tty: L{FilePath}
  586. @see: L{SSHCommandClientEndpoint.newConnection}
  587. """
  588. self.reactor = reactor
  589. self.hostname = hostname
  590. if port is not None:
  591. self.port = port
  592. self.command = command
  593. self.username = username
  594. self.keys = keys
  595. self.password = password
  596. self.agentEndpoint = agentEndpoint
  597. if knownHosts is None:
  598. knownHosts = self._knownHosts()
  599. self.knownHosts = knownHosts
  600. if ui is None:
  601. ui = ConsoleUI(self._opener)
  602. self.ui = ui
  603. self.tty = tty
  604. def _opener(self):
  605. """
  606. Open the tty if possible, otherwise give back a file-like object from
  607. which C{b"no"} can be read.
  608. For use as the opener argument to L{ConsoleUI}.
  609. """
  610. try:
  611. return self.tty.open("rb+")
  612. except:
  613. # Give back a file-like object from which can be read a byte string
  614. # that KnownHostsFile recognizes as rejecting some option (b"no").
  615. return _ReadFile(b"no")
  616. @classmethod
  617. def _knownHosts(cls):
  618. """
  619. @return: A L{KnownHostsFile} instance pointed at the user's personal
  620. I{known hosts} file.
  621. @type: L{KnownHostsFile}
  622. """
  623. return KnownHostsFile.fromPath(FilePath(expanduser(cls._KNOWN_HOSTS)))
  624. def secureConnection(self):
  625. """
  626. Create and return a new SSH connection which has been secured and on
  627. which authentication has already happened.
  628. @return: A L{Deferred} which fires with the ready-to-use connection or
  629. with a failure if something prevents the connection from being
  630. setup, secured, or authenticated.
  631. """
  632. protocol = _CommandTransport(self)
  633. ready = protocol.connectionReady
  634. sshClient = TCP4ClientEndpoint(
  635. self.reactor, nativeString(self.hostname), self.port)
  636. d = connectProtocol(sshClient, protocol)
  637. d.addCallback(lambda ignored: ready)
  638. return d
  639. def cleanupConnection(self, connection, immediate):
  640. """
  641. Clean up the connection by closing it. The command running on the
  642. endpoint has ended so the connection is no longer needed.
  643. @param connection: The L{SSHConnection} to close.
  644. @type connection: L{SSHConnection}
  645. @param immediate: Whether to close connection immediately.
  646. @type immediate: L{bool}.
  647. """
  648. if immediate:
  649. # We're assuming the underlying connection is an ITCPTransport,
  650. # which is what the current implementation is restricted to:
  651. connection.transport.transport.abortConnection()
  652. else:
  653. connection.transport.loseConnection()
  654. @implementer(_ISSHConnectionCreator)
  655. class _ExistingConnectionHelper(object):
  656. """
  657. L{_ExistingConnectionHelper} implements L{_ISSHConnectionCreator} by
  658. handing out an existing SSH connection which is supplied to its
  659. initializer.
  660. """
  661. def __init__(self, connection):
  662. """
  663. @param connection: See L{SSHCommandClientEndpoint.existingConnection}'s
  664. C{connection} parameter.
  665. """
  666. self.connection = connection
  667. def secureConnection(self):
  668. """
  669. @return: A L{Deferred} that fires synchronously with the
  670. already-established connection object.
  671. """
  672. return succeed(self.connection)
  673. def cleanupConnection(self, connection, immediate):
  674. """
  675. Do not do any cleanup on the connection. Leave that responsibility to
  676. whatever code created it in the first place.
  677. @param connection: The L{SSHConnection} which will not be modified in
  678. any way.
  679. @type connection: L{SSHConnection}
  680. @param immediate: An argument which will be ignored.
  681. @type immediate: L{bool}.
  682. """