Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

channel.py 9.7KB

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