manhole.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. # Copyright 2016 OpenMarket Ltd
  2. # Copyright 2019 New Vector Ltd.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import inspect
  16. import sys
  17. import traceback
  18. from typing import Any, Dict, Optional
  19. from twisted.conch import manhole_ssh
  20. from twisted.conch.insults import insults
  21. from twisted.conch.manhole import ColoredManhole, ManholeInterpreter
  22. from twisted.conch.ssh.keys import Key
  23. from twisted.cred import checkers, portal
  24. from twisted.internet import defer
  25. from twisted.internet.protocol import ServerFactory
  26. from synapse.config.server import ManholeConfig
  27. PUBLIC_KEY = (
  28. "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHhGATaW4KhE23+7nrH4jFx3yLq9OjaEs5"
  29. "XALqeK+7385NlLja3DE/DO9mGhnd9+bAy39EKT3sTV6+WXQ4yD0TvEEyUEMtjWkSEm6U32+C"
  30. "DaS3TW/vPBUMeJQwq+Ydcif1UlnpXrDDTamD0AU9VaEvHq+3HAkipqn0TGpKON6aqk4vauDx"
  31. "oXSsV5TXBVrxP/y7HpMOpU4GUWsaaacBTKKNnUaQB4UflvydaPJUuwdaCUJGTMjbhWrjVfK+"
  32. "jslseSPxU6XvrkZMyCr4znxvuDxjMk1RGIdO7v+rbBMLEgqtSMNqJbYeVCnj2CFgc3fcTcld"
  33. "X2uOJDrJb/WRlHulthCh"
  34. )
  35. PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY-----
  36. MIIEpQIBAAKCAQEAx4RgE2luCoRNt/u56x+Ixcd8i6vTo2hLOVwC6nivu9/OTZS4
  37. 2twxPwzvZhoZ3ffmwMt/RCk97E1evll0OMg9E7xBMlBDLY1pEhJulN9vgg2kt01v
  38. 7zwVDHiUMKvmHXIn9VJZ6V6ww02pg9AFPVWhLx6vtxwJIqap9ExqSjjemqpOL2rg
  39. 8aF0rFeU1wVa8T/8ux6TDqVOBlFrGmmnAUyijZ1GkAeFH5b8nWjyVLsHWglCRkzI
  40. 24Vq41Xyvo7JbHkj8VOl765GTMgq+M58b7g8YzJNURiHTu7/q2wTCxIKrUjDaiW2
  41. HlQp49ghYHN33E3JXV9rjiQ6yW/1kZR7pbYQoQIDAQABAoIBAQC8KJ0q8Wzzwh5B
  42. esa1dQHZ8+4DEsL/Amae66VcVwD0X3cCN1W2IZ7X5W0Ij2kBqr8V51RYhcR+S+Ek
  43. BtzSiBUBvbKGrqcMGKaUgomDIMzai99hd0gvCCyZnEW1OQhFkNkaRNXCfqiZJ27M
  44. fqvSUiU2eOwh9fCvmxoA6Of8o3FbzcJ+1GMcobWRllDtLmj6lgVbDzuA+0jC5daB
  45. 9Tj1pBzu3wn3ufxiS+gBnJ+7NcXH3E73lqCcPa2ufbZ1haxfiGCnRIhFXuQDgxFX
  46. vKdEfDgtvas6r1ahGbc+b/q8E8fZT7cABuIU4yfOORK+MhpyWbvoyyzuVGKj3PKt
  47. KSPJu5CZAoGBAOkoJfAVyYteqKcmGTanGqQnAY43CaYf6GdSPX/jg+JmKZg0zqMC
  48. jWZUtPb93i+jnOInbrnuHOiHAxI8wmhEPed28H2lC/LU8PzlqFkZXKFZ4vLOhhRB
  49. /HeHCFIDosPFlohWi3b+GAjD7sXgnIuGmnXWe2ea/TS3yersifDEoKKjAoGBANsQ
  50. gJX2cJv1c3jhdgcs8vAt5zIOKcCLTOr/QPmVf/kxjNgndswcKHwsxE/voTO9q+TF
  51. v/6yCSTxAdjuKz1oIYWgi/dZo82bBKWxNRpgrGviU3/zwxiHlyIXUhzQu78q3VS/
  52. 7S1XVbc7qMV++XkYKHPVD+nVG/gGzFxumX7MLXfrAoGBAJit9cn2OnjNj9uFE1W6
  53. r7N254ndeLAUjPe73xH0RtTm2a4WRopwjW/JYIetTuYbWgyujc+robqTTuuOZjAp
  54. H/CG7o0Ym251CypQqaFO/l2aowclPp/dZhpPjp9GSjuxFBZLtiBB3DNBOwbRQzIK
  55. /vLTdRQvZkgzYkI4i0vjNt3JAoGBANP8HSKBLymMlShlrSx2b8TB9tc2Y2riohVJ
  56. 2ttqs0M2kt/dGJWdrgOz4mikL+983Olt/0P9juHDoxEEMK2kpcPEv40lnmBpYU7h
  57. s8yJvnBLvJe2EJYdJ8AipyAhUX1FgpbvfxmASP8eaUxsegeXvBWTGWojAoS6N2o+
  58. 0KSl+l3vAoGAFqm0gO9f/Q1Se60YQd4l2PZeMnJFv0slpgHHUwegmd6wJhOD7zJ1
  59. CkZcXwiv7Nog7AI9qKJEUXLjoqL+vJskBzSOqU3tcd670YQMi1aXSXJqYE202K7o
  60. EddTrx3TNpr1D5m/f+6mnXWrc8u9y1+GNx9yz889xMjIBTBI9KqaaOs=
  61. -----END RSA PRIVATE KEY-----"""
  62. def manhole(settings: ManholeConfig, globals: Dict[str, Any]) -> ServerFactory:
  63. """Starts a ssh listener with password authentication using
  64. the given username and password. Clients connecting to the ssh
  65. listener will find themselves in a colored python shell with
  66. the supplied globals.
  67. Args:
  68. username: The username ssh clients should auth with.
  69. password: The password ssh clients should auth with.
  70. globals: The variables to expose in the shell.
  71. Returns:
  72. A factory to pass to ``listenTCP``
  73. """
  74. username = settings.username
  75. password = settings.password.encode("ascii")
  76. priv_key = settings.priv_key
  77. if priv_key is None:
  78. priv_key = Key.fromString(PRIVATE_KEY)
  79. pub_key = settings.pub_key
  80. if pub_key is None:
  81. pub_key = Key.fromString(PUBLIC_KEY)
  82. checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(**{username: password})
  83. rlm = manhole_ssh.TerminalRealm()
  84. # mypy ignored here because:
  85. # - can't deduce types of lambdas
  86. # - variable is Type[ServerProtocol], expr is Callable[[], ServerProtocol]
  87. rlm.chainedProtocolFactory = lambda: insults.ServerProtocol( # type: ignore[misc,assignment]
  88. SynapseManhole, dict(globals, __name__="__console__")
  89. )
  90. factory = manhole_ssh.ConchFactory(portal.Portal(rlm, [checker]))
  91. # conch has the wrong type on these dicts (says bytes to bytes,
  92. # should be bytes to Keys judging by how it's used).
  93. factory.privateKeys[b"ssh-rsa"] = priv_key # type: ignore[assignment]
  94. factory.publicKeys[b"ssh-rsa"] = pub_key # type: ignore[assignment]
  95. # ConchFactory is a Factory, not a ServerFactory, but they are identical.
  96. return factory # type: ignore[return-value]
  97. class SynapseManhole(ColoredManhole):
  98. """Overrides connectionMade to create our own ManholeInterpreter"""
  99. def connectionMade(self) -> None:
  100. super().connectionMade()
  101. # replace the manhole interpreter with our own impl
  102. self.interpreter = SynapseManholeInterpreter(self, self.namespace)
  103. # this would also be a good place to add more keyHandlers.
  104. class SynapseManholeInterpreter(ManholeInterpreter):
  105. def showsyntaxerror(self, filename: Optional[str] = None) -> None:
  106. """Display the syntax error that just occurred.
  107. Overrides the base implementation, ignoring sys.excepthook. We always want
  108. any syntax errors to be sent to the terminal, rather than sentry.
  109. """
  110. type, value, tb = sys.exc_info()
  111. assert value is not None
  112. sys.last_type = type
  113. sys.last_value = value
  114. sys.last_traceback = tb
  115. if filename and type is SyntaxError:
  116. # Work hard to stuff the correct filename in the exception
  117. try:
  118. msg, (dummy_filename, lineno, offset, line) = value.args
  119. except ValueError:
  120. # Not the format we expect; leave it alone
  121. pass
  122. else:
  123. # Stuff in the right filename
  124. value = SyntaxError(msg, (filename, lineno, offset, line))
  125. sys.last_value = value
  126. lines = traceback.format_exception_only(type, value)
  127. self.write("".join(lines))
  128. def showtraceback(self) -> None:
  129. """Display the exception that just occurred.
  130. Overrides the base implementation, ignoring sys.excepthook. We always want
  131. any syntax errors to be sent to the terminal, rather than sentry.
  132. """
  133. sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
  134. sys.last_traceback = last_tb
  135. assert last_tb is not None
  136. try:
  137. # We remove the first stack item because it is our own code.
  138. lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
  139. self.write("".join(lines))
  140. finally:
  141. # On the line below, last_tb and ei appear to be dead.
  142. # It's unclear whether there is a reason behind this line.
  143. # It conceivably could be because an exception raised in this block
  144. # will keep the local frame (containing these local variables) around.
  145. # This was adapted taken from CPython's Lib/code.py; see here:
  146. # https://github.com/python/cpython/blob/4dc4300c686f543d504ab6fa9fe600eaf11bb695/Lib/code.py#L131-L150
  147. last_tb = ei = None # type: ignore
  148. def displayhook(self, obj: Any) -> None:
  149. """
  150. We override the displayhook so that we automatically convert coroutines
  151. into Deferreds. (Our superclass' displayhook will take care of the rest,
  152. by displaying the Deferred if it's ready, or registering a callback
  153. if it's not).
  154. """
  155. if inspect.iscoroutine(obj):
  156. super().displayhook(defer.ensureDeferred(obj))
  157. else:
  158. super().displayhook(obj)