protocol.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. """
  2. Parser for the Telnet protocol. (Not a complete implementation of the telnet
  3. specification, but sufficient for a command line interface.)
  4. Inspired by `Twisted.conch.telnet`.
  5. """
  6. from __future__ import unicode_literals
  7. import struct
  8. from six import int2byte, binary_type, iterbytes
  9. from .log import logger
  10. __all__ = (
  11. 'TelnetProtocolParser',
  12. )
  13. # Telnet constants.
  14. NOP = int2byte(0)
  15. SGA = int2byte(3)
  16. IAC = int2byte(255)
  17. DO = int2byte(253)
  18. DONT = int2byte(254)
  19. LINEMODE = int2byte(34)
  20. SB = int2byte(250)
  21. WILL = int2byte(251)
  22. WONT = int2byte(252)
  23. MODE = int2byte(1)
  24. SE = int2byte(240)
  25. ECHO = int2byte(1)
  26. NAWS = int2byte(31)
  27. LINEMODE = int2byte(34)
  28. SUPPRESS_GO_AHEAD = int2byte(3)
  29. DM = int2byte(242)
  30. BRK = int2byte(243)
  31. IP = int2byte(244)
  32. AO = int2byte(245)
  33. AYT = int2byte(246)
  34. EC = int2byte(247)
  35. EL = int2byte(248)
  36. GA = int2byte(249)
  37. class TelnetProtocolParser(object):
  38. """
  39. Parser for the Telnet protocol.
  40. Usage::
  41. def data_received(data):
  42. print(data)
  43. def size_received(rows, columns):
  44. print(rows, columns)
  45. p = TelnetProtocolParser(data_received, size_received)
  46. p.feed(binary_data)
  47. """
  48. def __init__(self, data_received_callback, size_received_callback):
  49. self.data_received_callback = data_received_callback
  50. self.size_received_callback = size_received_callback
  51. self._parser = self._parse_coroutine()
  52. self._parser.send(None)
  53. def received_data(self, data):
  54. self.data_received_callback(data)
  55. def do_received(self, data):
  56. """ Received telnet DO command. """
  57. logger.info('DO %r', data)
  58. def dont_received(self, data):
  59. """ Received telnet DONT command. """
  60. logger.info('DONT %r', data)
  61. def will_received(self, data):
  62. """ Received telnet WILL command. """
  63. logger.info('WILL %r', data)
  64. def wont_received(self, data):
  65. """ Received telnet WONT command. """
  66. logger.info('WONT %r', data)
  67. def command_received(self, command, data):
  68. if command == DO:
  69. self.do_received(data)
  70. elif command == DONT:
  71. self.dont_received(data)
  72. elif command == WILL:
  73. self.will_received(data)
  74. elif command == WONT:
  75. self.wont_received(data)
  76. else:
  77. logger.info('command received %r %r', command, data)
  78. def naws(self, data):
  79. """
  80. Received NAWS. (Window dimensions.)
  81. """
  82. if len(data) == 4:
  83. # NOTE: the first parameter of struct.unpack should be
  84. # a 'str' object. Both on Py2/py3. This crashes on OSX
  85. # otherwise.
  86. columns, rows = struct.unpack(str('!HH'), data)
  87. self.size_received_callback(rows, columns)
  88. else:
  89. logger.warning('Wrong number of NAWS bytes')
  90. def negotiate(self, data):
  91. """
  92. Got negotiate data.
  93. """
  94. command, payload = data[0:1], data[1:]
  95. assert isinstance(command, bytes)
  96. if command == NAWS:
  97. self.naws(payload)
  98. else:
  99. logger.info('Negotiate (%r got bytes)', len(data))
  100. def _parse_coroutine(self):
  101. """
  102. Parser state machine.
  103. Every 'yield' expression returns the next byte.
  104. """
  105. while True:
  106. d = yield
  107. if d == int2byte(0):
  108. pass # NOP
  109. # Go to state escaped.
  110. elif d == IAC:
  111. d2 = yield
  112. if d2 == IAC:
  113. self.received_data(d2)
  114. # Handle simple commands.
  115. elif d2 in (NOP, DM, BRK, IP, AO, AYT, EC, EL, GA):
  116. self.command_received(d2, None)
  117. # Handle IAC-[DO/DONT/WILL/WONT] commands.
  118. elif d2 in (DO, DONT, WILL, WONT):
  119. d3 = yield
  120. self.command_received(d2, d3)
  121. # Subnegotiation
  122. elif d2 == SB:
  123. # Consume everything until next IAC-SE
  124. data = []
  125. while True:
  126. d3 = yield
  127. if d3 == IAC:
  128. d4 = yield
  129. if d4 == SE:
  130. break
  131. else:
  132. data.append(d4)
  133. else:
  134. data.append(d3)
  135. self.negotiate(b''.join(data))
  136. else:
  137. self.received_data(d)
  138. def feed(self, data):
  139. """
  140. Feed data to the parser.
  141. """
  142. assert isinstance(data, binary_type)
  143. for b in iterbytes(data):
  144. self._parser.send(int2byte(b))