connection.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. # -*- test-case-name: twisted.conch.test.test_connection -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. This module contains the implementation of the ssh-connection service, which
  6. allows access to the shell and port-forwarding.
  7. Maintainer: Paul Swartz
  8. """
  9. from __future__ import division, absolute_import
  10. import struct
  11. from twisted.conch.ssh import service, common
  12. from twisted.conch import error
  13. from twisted.internet import defer
  14. from twisted.python import log
  15. from twisted.python.compat import (
  16. networkString, nativeString, long, _bytesChr as chr)
  17. class SSHConnection(service.SSHService):
  18. """
  19. An implementation of the 'ssh-connection' service. It is used to
  20. multiplex multiple channels over the single SSH connection.
  21. @ivar localChannelID: the next number to use as a local channel ID.
  22. @type localChannelID: L{int}
  23. @ivar channels: a L{dict} mapping a local channel ID to C{SSHChannel}
  24. subclasses.
  25. @type channels: L{dict}
  26. @ivar localToRemoteChannel: a L{dict} mapping a local channel ID to a
  27. remote channel ID.
  28. @type localToRemoteChannel: L{dict}
  29. @ivar channelsToRemoteChannel: a L{dict} mapping a C{SSHChannel} subclass
  30. to remote channel ID.
  31. @type channelsToRemoteChannel: L{dict}
  32. @ivar deferreds: a L{dict} mapping a local channel ID to a C{list} of
  33. C{Deferreds} for outstanding channel requests. Also, the 'global'
  34. key stores the C{list} of pending global request C{Deferred}s.
  35. """
  36. name = b'ssh-connection'
  37. def __init__(self):
  38. self.localChannelID = 0 # this is the current # to use for channel ID
  39. self.localToRemoteChannel = {} # local channel ID -> remote channel ID
  40. self.channels = {} # local channel ID -> subclass of SSHChannel
  41. self.channelsToRemoteChannel = {} # subclass of SSHChannel ->
  42. # remote channel ID
  43. self.deferreds = {"global": []} # local channel -> list of deferreds
  44. # for pending requests or 'global' -> list of
  45. # deferreds for global requests
  46. self.transport = None # gets set later
  47. def serviceStarted(self):
  48. if hasattr(self.transport, 'avatar'):
  49. self.transport.avatar.conn = self
  50. def serviceStopped(self):
  51. """
  52. Called when the connection is stopped.
  53. """
  54. for channel in list(self.channels.values()):
  55. self.channelClosed(channel)
  56. self._cleanupGlobalDeferreds()
  57. def _cleanupGlobalDeferreds(self):
  58. """
  59. All pending requests that have returned a deferred must be errbacked
  60. when this service is stopped, otherwise they might be left uncalled and
  61. uncallable.
  62. """
  63. for d in self.deferreds["global"]:
  64. d.errback(error.ConchError("Connection stopped."))
  65. del self.deferreds["global"][:]
  66. # packet methods
  67. def ssh_GLOBAL_REQUEST(self, packet):
  68. """
  69. The other side has made a global request. Payload::
  70. string request type
  71. bool want reply
  72. <request specific data>
  73. This dispatches to self.gotGlobalRequest.
  74. """
  75. requestType, rest = common.getNS(packet)
  76. wantReply, rest = ord(rest[0:1]), rest[1:]
  77. ret = self.gotGlobalRequest(requestType, rest)
  78. if wantReply:
  79. reply = MSG_REQUEST_FAILURE
  80. data = b''
  81. if ret:
  82. reply = MSG_REQUEST_SUCCESS
  83. if isinstance(ret, (tuple, list)):
  84. data = ret[1]
  85. self.transport.sendPacket(reply, data)
  86. def ssh_REQUEST_SUCCESS(self, packet):
  87. """
  88. Our global request succeeded. Get the appropriate Deferred and call
  89. it back with the packet we received.
  90. """
  91. log.msg('RS')
  92. self.deferreds['global'].pop(0).callback(packet)
  93. def ssh_REQUEST_FAILURE(self, packet):
  94. """
  95. Our global request failed. Get the appropriate Deferred and errback
  96. it with the packet we received.
  97. """
  98. log.msg('RF')
  99. self.deferreds['global'].pop(0).errback(
  100. error.ConchError('global request failed', packet))
  101. def ssh_CHANNEL_OPEN(self, packet):
  102. """
  103. The other side wants to get a channel. Payload::
  104. string channel name
  105. uint32 remote channel number
  106. uint32 remote window size
  107. uint32 remote maximum packet size
  108. <channel specific data>
  109. We get a channel from self.getChannel(), give it a local channel number
  110. and notify the other side. Then notify the channel by calling its
  111. channelOpen method.
  112. """
  113. channelType, rest = common.getNS(packet)
  114. senderChannel, windowSize, maxPacket = struct.unpack('>3L', rest[:12])
  115. packet = rest[12:]
  116. try:
  117. channel = self.getChannel(channelType, windowSize, maxPacket,
  118. packet)
  119. localChannel = self.localChannelID
  120. self.localChannelID += 1
  121. channel.id = localChannel
  122. self.channels[localChannel] = channel
  123. self.channelsToRemoteChannel[channel] = senderChannel
  124. self.localToRemoteChannel[localChannel] = senderChannel
  125. self.transport.sendPacket(MSG_CHANNEL_OPEN_CONFIRMATION,
  126. struct.pack('>4L', senderChannel, localChannel,
  127. channel.localWindowSize,
  128. channel.localMaxPacket)+channel.specificData)
  129. log.callWithLogger(channel, channel.channelOpen, packet)
  130. except Exception as e:
  131. log.err(e, 'channel open failed')
  132. if isinstance(e, error.ConchError):
  133. textualInfo, reason = e.args
  134. if isinstance(textualInfo, (int, long)):
  135. # See #3657 and #3071
  136. textualInfo, reason = reason, textualInfo
  137. else:
  138. reason = OPEN_CONNECT_FAILED
  139. textualInfo = "unknown failure"
  140. self.transport.sendPacket(
  141. MSG_CHANNEL_OPEN_FAILURE,
  142. struct.pack('>2L', senderChannel, reason) +
  143. common.NS(networkString(textualInfo)) + common.NS(b''))
  144. def ssh_CHANNEL_OPEN_CONFIRMATION(self, packet):
  145. """
  146. The other side accepted our MSG_CHANNEL_OPEN request. Payload::
  147. uint32 local channel number
  148. uint32 remote channel number
  149. uint32 remote window size
  150. uint32 remote maximum packet size
  151. <channel specific data>
  152. Find the channel using the local channel number and notify its
  153. channelOpen method.
  154. """
  155. (localChannel, remoteChannel, windowSize,
  156. maxPacket) = struct.unpack('>4L', packet[: 16])
  157. specificData = packet[16:]
  158. channel = self.channels[localChannel]
  159. channel.conn = self
  160. self.localToRemoteChannel[localChannel] = remoteChannel
  161. self.channelsToRemoteChannel[channel] = remoteChannel
  162. channel.remoteWindowLeft = windowSize
  163. channel.remoteMaxPacket = maxPacket
  164. log.callWithLogger(channel, channel.channelOpen, specificData)
  165. def ssh_CHANNEL_OPEN_FAILURE(self, packet):
  166. """
  167. The other side did not accept our MSG_CHANNEL_OPEN request. Payload::
  168. uint32 local channel number
  169. uint32 reason code
  170. string reason description
  171. Find the channel using the local channel number and notify it by
  172. calling its openFailed() method.
  173. """
  174. localChannel, reasonCode = struct.unpack('>2L', packet[:8])
  175. reasonDesc = common.getNS(packet[8:])[0]
  176. channel = self.channels[localChannel]
  177. del self.channels[localChannel]
  178. channel.conn = self
  179. reason = error.ConchError(reasonDesc, reasonCode)
  180. log.callWithLogger(channel, channel.openFailed, reason)
  181. def ssh_CHANNEL_WINDOW_ADJUST(self, packet):
  182. """
  183. The other side is adding bytes to its window. Payload::
  184. uint32 local channel number
  185. uint32 bytes to add
  186. Call the channel's addWindowBytes() method to add new bytes to the
  187. remote window.
  188. """
  189. localChannel, bytesToAdd = struct.unpack('>2L', packet[:8])
  190. channel = self.channels[localChannel]
  191. log.callWithLogger(channel, channel.addWindowBytes, bytesToAdd)
  192. def ssh_CHANNEL_DATA(self, packet):
  193. """
  194. The other side is sending us data. Payload::
  195. uint32 local channel number
  196. string data
  197. Check to make sure the other side hasn't sent too much data (more
  198. than what's in the window, or more than the maximum packet size). If
  199. they have, close the channel. Otherwise, decrease the available
  200. window and pass the data to the channel's dataReceived().
  201. """
  202. localChannel, dataLength = struct.unpack('>2L', packet[:8])
  203. channel = self.channels[localChannel]
  204. # XXX should this move to dataReceived to put client in charge?
  205. if (dataLength > channel.localWindowLeft or
  206. dataLength > channel.localMaxPacket): # more data than we want
  207. log.callWithLogger(channel, log.msg, 'too much data')
  208. self.sendClose(channel)
  209. return
  210. #packet = packet[:channel.localWindowLeft+4]
  211. data = common.getNS(packet[4:])[0]
  212. channel.localWindowLeft -= dataLength
  213. if channel.localWindowLeft < channel.localWindowSize // 2:
  214. self.adjustWindow(channel, channel.localWindowSize - \
  215. channel.localWindowLeft)
  216. #log.msg('local window left: %s/%s' % (channel.localWindowLeft,
  217. # channel.localWindowSize))
  218. log.callWithLogger(channel, channel.dataReceived, data)
  219. def ssh_CHANNEL_EXTENDED_DATA(self, packet):
  220. """
  221. The other side is sending us exteneded data. Payload::
  222. uint32 local channel number
  223. uint32 type code
  224. string data
  225. Check to make sure the other side hasn't sent too much data (more
  226. than what's in the window, or than the maximum packet size). If
  227. they have, close the channel. Otherwise, decrease the available
  228. window and pass the data and type code to the channel's
  229. extReceived().
  230. """
  231. localChannel, typeCode, dataLength = struct.unpack('>3L', packet[:12])
  232. channel = self.channels[localChannel]
  233. if (dataLength > channel.localWindowLeft or
  234. dataLength > channel.localMaxPacket):
  235. log.callWithLogger(channel, log.msg, 'too much extdata')
  236. self.sendClose(channel)
  237. return
  238. data = common.getNS(packet[8:])[0]
  239. channel.localWindowLeft -= dataLength
  240. if channel.localWindowLeft < channel.localWindowSize // 2:
  241. self.adjustWindow(channel, channel.localWindowSize -
  242. channel.localWindowLeft)
  243. log.callWithLogger(channel, channel.extReceived, typeCode, data)
  244. def ssh_CHANNEL_EOF(self, packet):
  245. """
  246. The other side is not sending any more data. Payload::
  247. uint32 local channel number
  248. Notify the channel by calling its eofReceived() method.
  249. """
  250. localChannel = struct.unpack('>L', packet[:4])[0]
  251. channel = self.channels[localChannel]
  252. log.callWithLogger(channel, channel.eofReceived)
  253. def ssh_CHANNEL_CLOSE(self, packet):
  254. """
  255. The other side is closing its end; it does not want to receive any
  256. more data. Payload::
  257. uint32 local channel number
  258. Notify the channnel by calling its closeReceived() method. If
  259. the channel has also sent a close message, call self.channelClosed().
  260. """
  261. localChannel = struct.unpack('>L', packet[:4])[0]
  262. channel = self.channels[localChannel]
  263. log.callWithLogger(channel, channel.closeReceived)
  264. channel.remoteClosed = True
  265. if channel.localClosed and channel.remoteClosed:
  266. self.channelClosed(channel)
  267. def ssh_CHANNEL_REQUEST(self, packet):
  268. """
  269. The other side is sending a request to a channel. Payload::
  270. uint32 local channel number
  271. string request name
  272. bool want reply
  273. <request specific data>
  274. Pass the message to the channel's requestReceived method. If the
  275. other side wants a reply, add callbacks which will send the
  276. reply.
  277. """
  278. localChannel = struct.unpack('>L', packet[:4])[0]
  279. requestType, rest = common.getNS(packet[4:])
  280. wantReply = ord(rest[0:1])
  281. channel = self.channels[localChannel]
  282. d = defer.maybeDeferred(log.callWithLogger, channel,
  283. channel.requestReceived, requestType, rest[1:])
  284. if wantReply:
  285. d.addCallback(self._cbChannelRequest, localChannel)
  286. d.addErrback(self._ebChannelRequest, localChannel)
  287. return d
  288. def _cbChannelRequest(self, result, localChannel):
  289. """
  290. Called back if the other side wanted a reply to a channel request. If
  291. the result is true, send a MSG_CHANNEL_SUCCESS. Otherwise, raise
  292. a C{error.ConchError}
  293. @param result: the value returned from the channel's requestReceived()
  294. method. If it's False, the request failed.
  295. @type result: L{bool}
  296. @param localChannel: the local channel ID of the channel to which the
  297. request was made.
  298. @type localChannel: L{int}
  299. @raises ConchError: if the result is False.
  300. """
  301. if not result:
  302. raise error.ConchError('failed request')
  303. self.transport.sendPacket(MSG_CHANNEL_SUCCESS, struct.pack('>L',
  304. self.localToRemoteChannel[localChannel]))
  305. def _ebChannelRequest(self, result, localChannel):
  306. """
  307. Called if the other wisde wanted a reply to the channel requeset and
  308. the channel request failed.
  309. @param result: a Failure, but it's not used.
  310. @param localChannel: the local channel ID of the channel to which the
  311. request was made.
  312. @type localChannel: L{int}
  313. """
  314. self.transport.sendPacket(MSG_CHANNEL_FAILURE, struct.pack('>L',
  315. self.localToRemoteChannel[localChannel]))
  316. def ssh_CHANNEL_SUCCESS(self, packet):
  317. """
  318. Our channel request to the other side succeeded. Payload::
  319. uint32 local channel number
  320. Get the C{Deferred} out of self.deferreds and call it back.
  321. """
  322. localChannel = struct.unpack('>L', packet[:4])[0]
  323. if self.deferreds.get(localChannel):
  324. d = self.deferreds[localChannel].pop(0)
  325. log.callWithLogger(self.channels[localChannel],
  326. d.callback, '')
  327. def ssh_CHANNEL_FAILURE(self, packet):
  328. """
  329. Our channel request to the other side failed. Payload::
  330. uint32 local channel number
  331. Get the C{Deferred} out of self.deferreds and errback it with a
  332. C{error.ConchError}.
  333. """
  334. localChannel = struct.unpack('>L', packet[:4])[0]
  335. if self.deferreds.get(localChannel):
  336. d = self.deferreds[localChannel].pop(0)
  337. log.callWithLogger(self.channels[localChannel],
  338. d.errback,
  339. error.ConchError('channel request failed'))
  340. # methods for users of the connection to call
  341. def sendGlobalRequest(self, request, data, wantReply=0):
  342. """
  343. Send a global request for this connection. Current this is only used
  344. for remote->local TCP forwarding.
  345. @type request: L{bytes}
  346. @type data: L{bytes}
  347. @type wantReply: L{bool}
  348. @rtype C{Deferred}/L{None}
  349. """
  350. self.transport.sendPacket(MSG_GLOBAL_REQUEST,
  351. common.NS(request)
  352. + (wantReply and b'\xff' or b'\x00')
  353. + data)
  354. if wantReply:
  355. d = defer.Deferred()
  356. self.deferreds['global'].append(d)
  357. return d
  358. def openChannel(self, channel, extra=b''):
  359. """
  360. Open a new channel on this connection.
  361. @type channel: subclass of C{SSHChannel}
  362. @type extra: L{bytes}
  363. """
  364. log.msg('opening channel %s with %s %s'%(self.localChannelID,
  365. channel.localWindowSize, channel.localMaxPacket))
  366. self.transport.sendPacket(MSG_CHANNEL_OPEN, common.NS(channel.name)
  367. + struct.pack('>3L', self.localChannelID,
  368. channel.localWindowSize, channel.localMaxPacket)
  369. + extra)
  370. channel.id = self.localChannelID
  371. self.channels[self.localChannelID] = channel
  372. self.localChannelID += 1
  373. def sendRequest(self, channel, requestType, data, wantReply=0):
  374. """
  375. Send a request to a channel.
  376. @type channel: subclass of C{SSHChannel}
  377. @type requestType: L{bytes}
  378. @type data: L{bytes}
  379. @type wantReply: L{bool}
  380. @rtype C{Deferred}/L{None}
  381. """
  382. if channel.localClosed:
  383. return
  384. log.msg('sending request %r' % (requestType))
  385. self.transport.sendPacket(MSG_CHANNEL_REQUEST, struct.pack('>L',
  386. self.channelsToRemoteChannel[channel])
  387. + common.NS(requestType)+chr(wantReply)
  388. + data)
  389. if wantReply:
  390. d = defer.Deferred()
  391. self.deferreds.setdefault(channel.id, []).append(d)
  392. return d
  393. def adjustWindow(self, channel, bytesToAdd):
  394. """
  395. Tell the other side that we will receive more data. This should not
  396. normally need to be called as it is managed automatically.
  397. @type channel: subclass of L{SSHChannel}
  398. @type bytesToAdd: L{int}
  399. """
  400. if channel.localClosed:
  401. return # we're already closed
  402. self.transport.sendPacket(MSG_CHANNEL_WINDOW_ADJUST, struct.pack('>2L',
  403. self.channelsToRemoteChannel[channel],
  404. bytesToAdd))
  405. log.msg('adding %i to %i in channel %i' % (bytesToAdd,
  406. channel.localWindowLeft, channel.id))
  407. channel.localWindowLeft += bytesToAdd
  408. def sendData(self, channel, data):
  409. """
  410. Send data to a channel. This should not normally be used: instead use
  411. channel.write(data) as it manages the window automatically.
  412. @type channel: subclass of L{SSHChannel}
  413. @type data: L{bytes}
  414. """
  415. if channel.localClosed:
  416. return # we're already closed
  417. self.transport.sendPacket(MSG_CHANNEL_DATA, struct.pack('>L',
  418. self.channelsToRemoteChannel[channel]) +
  419. common.NS(data))
  420. def sendExtendedData(self, channel, dataType, data):
  421. """
  422. Send extended data to a channel. This should not normally be used:
  423. instead use channel.writeExtendedData(data, dataType) as it manages
  424. the window automatically.
  425. @type channel: subclass of L{SSHChannel}
  426. @type dataType: L{int}
  427. @type data: L{bytes}
  428. """
  429. if channel.localClosed:
  430. return # we're already closed
  431. self.transport.sendPacket(MSG_CHANNEL_EXTENDED_DATA, struct.pack('>2L',
  432. self.channelsToRemoteChannel[channel],dataType) \
  433. + common.NS(data))
  434. def sendEOF(self, channel):
  435. """
  436. Send an EOF (End of File) for a channel.
  437. @type channel: subclass of L{SSHChannel}
  438. """
  439. if channel.localClosed:
  440. return # we're already closed
  441. log.msg('sending eof')
  442. self.transport.sendPacket(MSG_CHANNEL_EOF, struct.pack('>L',
  443. self.channelsToRemoteChannel[channel]))
  444. def sendClose(self, channel):
  445. """
  446. Close a channel.
  447. @type channel: subclass of L{SSHChannel}
  448. """
  449. if channel.localClosed:
  450. return # we're already closed
  451. log.msg('sending close %i' % channel.id)
  452. self.transport.sendPacket(MSG_CHANNEL_CLOSE, struct.pack('>L',
  453. self.channelsToRemoteChannel[channel]))
  454. channel.localClosed = True
  455. if channel.localClosed and channel.remoteClosed:
  456. self.channelClosed(channel)
  457. # methods to override
  458. def getChannel(self, channelType, windowSize, maxPacket, data):
  459. """
  460. The other side requested a channel of some sort.
  461. channelType is the type of channel being requested,
  462. windowSize is the initial size of the remote window,
  463. maxPacket is the largest packet we should send,
  464. data is any other packet data (often nothing).
  465. We return a subclass of L{SSHChannel}.
  466. By default, this dispatches to a method 'channel_channelType' with any
  467. non-alphanumerics in the channelType replace with _'s. If it cannot
  468. find a suitable method, it returns an OPEN_UNKNOWN_CHANNEL_TYPE error.
  469. The method is called with arguments of windowSize, maxPacket, data.
  470. @type channelType: L{bytes}
  471. @type windowSize: L{int}
  472. @type maxPacket: L{int}
  473. @type data: L{bytes}
  474. @rtype: subclass of L{SSHChannel}/L{tuple}
  475. """
  476. log.msg('got channel %r request' % (channelType))
  477. if hasattr(self.transport, "avatar"): # this is a server!
  478. chan = self.transport.avatar.lookupChannel(channelType,
  479. windowSize,
  480. maxPacket,
  481. data)
  482. else:
  483. channelType = channelType.translate(TRANSLATE_TABLE)
  484. attr = 'channel_%s' % nativeString(channelType)
  485. f = getattr(self, attr, None)
  486. if f is not None:
  487. chan = f(windowSize, maxPacket, data)
  488. else:
  489. chan = None
  490. if chan is None:
  491. raise error.ConchError('unknown channel',
  492. OPEN_UNKNOWN_CHANNEL_TYPE)
  493. else:
  494. chan.conn = self
  495. return chan
  496. def gotGlobalRequest(self, requestType, data):
  497. """
  498. We got a global request. pretty much, this is just used by the client
  499. to request that we forward a port from the server to the client.
  500. Returns either:
  501. - 1: request accepted
  502. - 1, <data>: request accepted with request specific data
  503. - 0: request denied
  504. By default, this dispatches to a method 'global_requestType' with
  505. -'s in requestType replaced with _'s. The found method is passed data.
  506. If this method cannot be found, this method returns 0. Otherwise, it
  507. returns the return value of that method.
  508. @type requestType: L{bytes}
  509. @type data: L{bytes}
  510. @rtype: L{int}/L{tuple}
  511. """
  512. log.msg('got global %s request' % requestType)
  513. if hasattr(self.transport, 'avatar'): # this is a server!
  514. return self.transport.avatar.gotGlobalRequest(requestType, data)
  515. requestType = nativeString(requestType.replace(b'-',b'_'))
  516. f = getattr(self, 'global_%s' % requestType, None)
  517. if not f:
  518. return 0
  519. return f(data)
  520. def channelClosed(self, channel):
  521. """
  522. Called when a channel is closed.
  523. It clears the local state related to the channel, and calls
  524. channel.closed().
  525. MAKE SURE YOU CALL THIS METHOD, even if you subclass L{SSHConnection}.
  526. If you don't, things will break mysteriously.
  527. @type channel: L{SSHChannel}
  528. """
  529. if channel in self.channelsToRemoteChannel: # actually open
  530. channel.localClosed = channel.remoteClosed = True
  531. del self.localToRemoteChannel[channel.id]
  532. del self.channels[channel.id]
  533. del self.channelsToRemoteChannel[channel]
  534. for d in self.deferreds.setdefault(channel.id, []):
  535. d.errback(error.ConchError("Channel closed."))
  536. del self.deferreds[channel.id][:]
  537. log.callWithLogger(channel, channel.closed)
  538. MSG_GLOBAL_REQUEST = 80
  539. MSG_REQUEST_SUCCESS = 81
  540. MSG_REQUEST_FAILURE = 82
  541. MSG_CHANNEL_OPEN = 90
  542. MSG_CHANNEL_OPEN_CONFIRMATION = 91
  543. MSG_CHANNEL_OPEN_FAILURE = 92
  544. MSG_CHANNEL_WINDOW_ADJUST = 93
  545. MSG_CHANNEL_DATA = 94
  546. MSG_CHANNEL_EXTENDED_DATA = 95
  547. MSG_CHANNEL_EOF = 96
  548. MSG_CHANNEL_CLOSE = 97
  549. MSG_CHANNEL_REQUEST = 98
  550. MSG_CHANNEL_SUCCESS = 99
  551. MSG_CHANNEL_FAILURE = 100
  552. OPEN_ADMINISTRATIVELY_PROHIBITED = 1
  553. OPEN_CONNECT_FAILED = 2
  554. OPEN_UNKNOWN_CHANNEL_TYPE = 3
  555. OPEN_RESOURCE_SHORTAGE = 4
  556. EXTENDED_DATA_STDERR = 1
  557. messages = {}
  558. for name, value in locals().copy().items():
  559. if name[:4] == 'MSG_':
  560. messages[value] = name # doesn't handle doubles
  561. import string
  562. alphanums = networkString(string.ascii_letters + string.digits)
  563. TRANSLATE_TABLE = b''.join([chr(i) in alphanums and chr(i) or b'_'
  564. for i in range(256)])
  565. SSHConnection.protocolMessages = messages