channel.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. # -*- test-case-name: twisted.conch.test.test_channel -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. The parent class for all the SSH Channels. Currently implemented channels
  6. are session, direct-tcp, and forwarded-tcp.
  7. Maintainer: Paul Swartz
  8. """
  9. from __future__ import division, absolute_import
  10. from zope.interface import implementer
  11. from twisted.python import log
  12. from twisted.python.compat import nativeString, intToBytes
  13. from twisted.internet import interfaces
  14. @implementer(interfaces.ITransport)
  15. class SSHChannel(log.Logger):
  16. """
  17. A class that represents a multiplexed channel over an SSH connection.
  18. The channel has a local window which is the maximum amount of data it will
  19. receive, and a remote which is the maximum amount of data the remote side
  20. will accept. There is also a maximum packet size for any individual data
  21. packet going each way.
  22. @ivar name: the name of the channel.
  23. @type name: L{bytes}
  24. @ivar localWindowSize: the maximum size of the local window in bytes.
  25. @type localWindowSize: L{int}
  26. @ivar localWindowLeft: how many bytes are left in the local window.
  27. @type localWindowLeft: L{int}
  28. @ivar localMaxPacket: the maximum size of packet we will accept in bytes.
  29. @type localMaxPacket: L{int}
  30. @ivar remoteWindowLeft: how many bytes are left in the remote window.
  31. @type remoteWindowLeft: L{int}
  32. @ivar remoteMaxPacket: the maximum size of a packet the remote side will
  33. accept in bytes.
  34. @type remoteMaxPacket: L{int}
  35. @ivar conn: the connection this channel is multiplexed through.
  36. @type conn: L{SSHConnection}
  37. @ivar data: any data to send to the other side when the channel is
  38. requested.
  39. @type data: L{bytes}
  40. @ivar avatar: an avatar for the logged-in user (if a server channel)
  41. @ivar localClosed: True if we aren't accepting more data.
  42. @type localClosed: L{bool}
  43. @ivar remoteClosed: True if the other side isn't accepting more data.
  44. @type remoteClosed: L{bool}
  45. """
  46. name = None # only needed for client channels
  47. def __init__(self, localWindow = 0, localMaxPacket = 0,
  48. remoteWindow = 0, remoteMaxPacket = 0,
  49. conn = None, data=None, avatar = None):
  50. self.localWindowSize = localWindow or 131072
  51. self.localWindowLeft = self.localWindowSize
  52. self.localMaxPacket = localMaxPacket or 32768
  53. self.remoteWindowLeft = remoteWindow
  54. self.remoteMaxPacket = remoteMaxPacket
  55. self.areWriting = 1
  56. self.conn = conn
  57. self.data = data
  58. self.avatar = avatar
  59. self.specificData = b''
  60. self.buf = b''
  61. self.extBuf = []
  62. self.closing = 0
  63. self.localClosed = 0
  64. self.remoteClosed = 0
  65. self.id = None # gets set later by SSHConnection
  66. def __str__(self):
  67. return nativeString(self.__bytes__())
  68. def __bytes__(self):
  69. """
  70. Return a byte string representation of the channel
  71. """
  72. name = self.name
  73. if not name:
  74. name = b'None'
  75. return (b'<SSHChannel ' + name +
  76. b' (lw ' + intToBytes(self.localWindowLeft) +
  77. b' rw ' + intToBytes(self.remoteWindowLeft) +
  78. b')>')
  79. def logPrefix(self):
  80. id = (self.id is not None and str(self.id)) or "unknown"
  81. name = self.name
  82. if name:
  83. name = nativeString(name)
  84. return "SSHChannel %s (%s) on %s" % (name, id,
  85. self.conn.logPrefix())
  86. def channelOpen(self, specificData):
  87. """
  88. Called when the channel is opened. specificData is any data that the
  89. other side sent us when opening the channel.
  90. @type specificData: L{bytes}
  91. """
  92. log.msg('channel open')
  93. def openFailed(self, reason):
  94. """
  95. Called when the open failed for some reason.
  96. reason.desc is a string descrption, reason.code the SSH error code.
  97. @type reason: L{error.ConchError}
  98. """
  99. log.msg('other side refused open\nreason: %s'% reason)
  100. def addWindowBytes(self, data):
  101. """
  102. Called when bytes are added to the remote window. By default it clears
  103. the data buffers.
  104. @type data: L{bytes}
  105. """
  106. self.remoteWindowLeft = self.remoteWindowLeft+data
  107. if not self.areWriting and not self.closing:
  108. self.areWriting = True
  109. self.startWriting()
  110. if self.buf:
  111. b = self.buf
  112. self.buf = b''
  113. self.write(b)
  114. if self.extBuf:
  115. b = self.extBuf
  116. self.extBuf = []
  117. for (type, data) in b:
  118. self.writeExtended(type, data)
  119. def requestReceived(self, requestType, data):
  120. """
  121. Called when a request is sent to this channel. By default it delegates
  122. to self.request_<requestType>.
  123. If this function returns true, the request succeeded, otherwise it
  124. failed.
  125. @type requestType: L{bytes}
  126. @type data: L{bytes}
  127. @rtype: L{bool}
  128. """
  129. foo = nativeString(requestType.replace(b'-', b'_'))
  130. f = getattr(self, 'request_%s'%foo, None)
  131. if f:
  132. return f(data)
  133. log.msg('unhandled request for %s'%requestType)
  134. return 0
  135. def dataReceived(self, data):
  136. """
  137. Called when we receive data.
  138. @type data: L{bytes}
  139. """
  140. log.msg('got data %s'%repr(data))
  141. def extReceived(self, dataType, data):
  142. """
  143. Called when we receive extended data (usually standard error).
  144. @type dataType: L{int}
  145. @type data: L{str}
  146. """
  147. log.msg('got extended data %s %s'%(dataType, repr(data)))
  148. def eofReceived(self):
  149. """
  150. Called when the other side will send no more data.
  151. """
  152. log.msg('remote eof')
  153. def closeReceived(self):
  154. """
  155. Called when the other side has closed the channel.
  156. """
  157. log.msg('remote close')
  158. self.loseConnection()
  159. def closed(self):
  160. """
  161. Called when the channel is closed. This means that both our side and
  162. the remote side have closed the channel.
  163. """
  164. log.msg('closed')
  165. def write(self, data):
  166. """
  167. Write some data to the channel. If there is not enough remote window
  168. available, buffer until it is. Otherwise, split the data into
  169. packets of length remoteMaxPacket and send them.
  170. @type data: L{bytes}
  171. """
  172. if self.buf:
  173. self.buf += data
  174. return
  175. top = len(data)
  176. if top > self.remoteWindowLeft:
  177. data, self.buf = (data[:self.remoteWindowLeft],
  178. data[self.remoteWindowLeft:])
  179. self.areWriting = 0
  180. self.stopWriting()
  181. top = self.remoteWindowLeft
  182. rmp = self.remoteMaxPacket
  183. write = self.conn.sendData
  184. r = range(0, top, rmp)
  185. for offset in r:
  186. write(self, data[offset: offset+rmp])
  187. self.remoteWindowLeft -= top
  188. if self.closing and not self.buf:
  189. self.loseConnection() # try again
  190. def writeExtended(self, dataType, data):
  191. """
  192. Send extended data to this channel. If there is not enough remote
  193. window available, buffer until there is. Otherwise, split the data
  194. into packets of length remoteMaxPacket and send them.
  195. @type dataType: L{int}
  196. @type data: L{bytes}
  197. """
  198. if self.extBuf:
  199. if self.extBuf[-1][0] == dataType:
  200. self.extBuf[-1][1] += data
  201. else:
  202. self.extBuf.append([dataType, data])
  203. return
  204. if len(data) > self.remoteWindowLeft:
  205. data, self.extBuf = (data[:self.remoteWindowLeft],
  206. [[dataType, data[self.remoteWindowLeft:]]])
  207. self.areWriting = 0
  208. self.stopWriting()
  209. while len(data) > self.remoteMaxPacket:
  210. self.conn.sendExtendedData(self, dataType,
  211. data[:self.remoteMaxPacket])
  212. data = data[self.remoteMaxPacket:]
  213. self.remoteWindowLeft -= self.remoteMaxPacket
  214. if data:
  215. self.conn.sendExtendedData(self, dataType, data)
  216. self.remoteWindowLeft -= len(data)
  217. if self.closing:
  218. self.loseConnection() # try again
  219. def writeSequence(self, data):
  220. """
  221. Part of the Transport interface. Write a list of strings to the
  222. channel.
  223. @type data: C{list} of L{str}
  224. """
  225. self.write(b''.join(data))
  226. def loseConnection(self):
  227. """
  228. Close the channel if there is no buferred data. Otherwise, note the
  229. request and return.
  230. """
  231. self.closing = 1
  232. if not self.buf and not self.extBuf:
  233. self.conn.sendClose(self)
  234. def getPeer(self):
  235. """
  236. See: L{ITransport.getPeer}
  237. @return: The remote address of this connection.
  238. @rtype: L{SSHTransportAddress}.
  239. """
  240. return self.conn.transport.getPeer()
  241. def getHost(self):
  242. """
  243. See: L{ITransport.getHost}
  244. @return: An address describing this side of the connection.
  245. @rtype: L{SSHTransportAddress}.
  246. """
  247. return self.conn.transport.getHost()
  248. def stopWriting(self):
  249. """
  250. Called when the remote buffer is full, as a hint to stop writing.
  251. This can be ignored, but it can be helpful.
  252. """
  253. def startWriting(self):
  254. """
  255. Called when the remote buffer has more room, as a hint to continue
  256. writing.
  257. """