manhole_tap.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. TAP plugin for creating telnet- and ssh-accessible manhole servers.
  5. @author: Jp Calderone
  6. """
  7. from zope.interface import implementer
  8. from twisted.application import service, strports
  9. from twisted.conch import manhole, manhole_ssh, telnet
  10. from twisted.conch.insults import insults
  11. from twisted.conch.ssh import keys
  12. from twisted.cred import checkers, portal
  13. from twisted.internet import protocol
  14. from twisted.python import filepath, usage
  15. class makeTelnetProtocol:
  16. def __init__(self, portal):
  17. self.portal = portal
  18. def __call__(self):
  19. auth = telnet.AuthenticatingTelnetProtocol
  20. args = (self.portal,)
  21. return telnet.TelnetTransport(auth, *args)
  22. class chainedProtocolFactory:
  23. def __init__(self, namespace):
  24. self.namespace = namespace
  25. def __call__(self):
  26. return insults.ServerProtocol(manhole.ColoredManhole, self.namespace)
  27. @implementer(portal.IRealm)
  28. class _StupidRealm:
  29. def __init__(self, proto, *a, **kw):
  30. self.protocolFactory = proto
  31. self.protocolArgs = a
  32. self.protocolKwArgs = kw
  33. def requestAvatar(self, avatarId, *interfaces):
  34. if telnet.ITelnetProtocol in interfaces:
  35. return (
  36. telnet.ITelnetProtocol,
  37. self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs),
  38. lambda: None,
  39. )
  40. raise NotImplementedError()
  41. class Options(usage.Options):
  42. optParameters = [
  43. [
  44. "telnetPort",
  45. "t",
  46. None,
  47. (
  48. "strports description of the address on which to listen for telnet "
  49. "connections"
  50. ),
  51. ],
  52. [
  53. "sshPort",
  54. "s",
  55. None,
  56. (
  57. "strports description of the address on which to listen for ssh "
  58. "connections"
  59. ),
  60. ],
  61. [
  62. "passwd",
  63. "p",
  64. "/etc/passwd",
  65. "name of a passwd(5)-format username/password file",
  66. ],
  67. [
  68. "sshKeyDir",
  69. None,
  70. "<USER DATA DIR>",
  71. "Directory where the autogenerated SSH key is kept.",
  72. ],
  73. ["sshKeyName", None, "server.key", "Filename of the autogenerated SSH key."],
  74. ["sshKeySize", None, 4096, "Size of the automatically generated SSH key."],
  75. ]
  76. def __init__(self):
  77. usage.Options.__init__(self)
  78. self["namespace"] = None
  79. def postOptions(self):
  80. if self["telnetPort"] is None and self["sshPort"] is None:
  81. raise usage.UsageError(
  82. "At least one of --telnetPort and --sshPort must be specified"
  83. )
  84. def makeService(options):
  85. """
  86. Create a manhole server service.
  87. @type options: L{dict}
  88. @param options: A mapping describing the configuration of
  89. the desired service. Recognized key/value pairs are::
  90. "telnetPort": strports description of the address on which
  91. to listen for telnet connections. If None,
  92. no telnet service will be started.
  93. "sshPort": strports description of the address on which to
  94. listen for ssh connections. If None, no ssh
  95. service will be started.
  96. "namespace": dictionary containing desired initial locals
  97. for manhole connections. If None, an empty
  98. dictionary will be used.
  99. "passwd": Name of a passwd(5)-format username/password file.
  100. "sshKeyDir": The folder that the SSH server key will be kept in.
  101. "sshKeyName": The filename of the key.
  102. "sshKeySize": The size of the key, in bits. Default is 4096.
  103. @rtype: L{twisted.application.service.IService}
  104. @return: A manhole service.
  105. """
  106. svc = service.MultiService()
  107. namespace = options["namespace"]
  108. if namespace is None:
  109. namespace = {}
  110. checker = checkers.FilePasswordDB(options["passwd"])
  111. if options["telnetPort"]:
  112. telnetRealm = _StupidRealm(
  113. telnet.TelnetBootstrapProtocol,
  114. insults.ServerProtocol,
  115. manhole.ColoredManhole,
  116. namespace,
  117. )
  118. telnetPortal = portal.Portal(telnetRealm, [checker])
  119. telnetFactory = protocol.ServerFactory()
  120. telnetFactory.protocol = makeTelnetProtocol(telnetPortal)
  121. telnetService = strports.service(options["telnetPort"], telnetFactory)
  122. telnetService.setServiceParent(svc)
  123. if options["sshPort"]:
  124. sshRealm = manhole_ssh.TerminalRealm()
  125. sshRealm.chainedProtocolFactory = chainedProtocolFactory(namespace)
  126. sshPortal = portal.Portal(sshRealm, [checker])
  127. sshFactory = manhole_ssh.ConchFactory(sshPortal)
  128. if options["sshKeyDir"] != "<USER DATA DIR>":
  129. keyDir = options["sshKeyDir"]
  130. else:
  131. from twisted.python._appdirs import getDataDirectory
  132. keyDir = getDataDirectory()
  133. keyLocation = filepath.FilePath(keyDir).child(options["sshKeyName"])
  134. sshKey = keys._getPersistentRSAKey(keyLocation, int(options["sshKeySize"]))
  135. sshFactory.publicKeys[b"ssh-rsa"] = sshKey
  136. sshFactory.privateKeys[b"ssh-rsa"] = sshKey
  137. sshService = strports.service(options["sshPort"], sshFactory)
  138. sshService.setServiceParent(svc)
  139. return svc