manhole_ssh.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. # -*- test-case-name: twisted.conch.test.test_manhole -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. insults/SSH integration support.
  6. @author: Jp Calderone
  7. """
  8. from typing import Dict
  9. from zope.interface import implementer
  10. from twisted.conch import avatar, error as econch, interfaces as iconch
  11. from twisted.conch.insults import insults
  12. from twisted.conch.ssh import factory, session
  13. from twisted.python import components
  14. class _Glue:
  15. """
  16. A feeble class for making one attribute look like another.
  17. This should be replaced with a real class at some point, probably.
  18. Try not to write new code that uses it.
  19. """
  20. def __init__(self, **kw):
  21. self.__dict__.update(kw)
  22. def __getattr__(self, name):
  23. raise AttributeError(self.name, "has no attribute", name)
  24. class TerminalSessionTransport:
  25. def __init__(self, proto, chainedProtocol, avatar, width, height):
  26. self.proto = proto
  27. self.avatar = avatar
  28. self.chainedProtocol = chainedProtocol
  29. protoSession = self.proto.session
  30. self.proto.makeConnection(
  31. _Glue(
  32. write=self.chainedProtocol.dataReceived,
  33. loseConnection=lambda: avatar.conn.sendClose(protoSession),
  34. name="SSH Proto Transport",
  35. )
  36. )
  37. def loseConnection():
  38. self.proto.loseConnection()
  39. self.chainedProtocol.makeConnection(
  40. _Glue(
  41. write=self.proto.write,
  42. loseConnection=loseConnection,
  43. name="Chained Proto Transport",
  44. )
  45. )
  46. # XXX TODO
  47. # chainedProtocol is supposed to be an ITerminalTransport,
  48. # maybe. That means perhaps its terminalProtocol attribute is
  49. # an ITerminalProtocol, it could be. So calling terminalSize
  50. # on that should do the right thing But it'd be nice to clean
  51. # this bit up.
  52. self.chainedProtocol.terminalProtocol.terminalSize(width, height)
  53. @implementer(iconch.ISession)
  54. class TerminalSession(components.Adapter):
  55. transportFactory = TerminalSessionTransport
  56. chainedProtocolFactory = insults.ServerProtocol
  57. def getPty(self, term, windowSize, attrs):
  58. self.height, self.width = windowSize[:2]
  59. def openShell(self, proto):
  60. self.transportFactory(
  61. proto,
  62. self.chainedProtocolFactory(),
  63. iconch.IConchUser(self.original),
  64. self.width,
  65. self.height,
  66. )
  67. def execCommand(self, proto, cmd):
  68. raise econch.ConchError("Cannot execute commands")
  69. def windowChanged(self, newWindowSize):
  70. # ISession.windowChanged
  71. raise NotImplementedError("Unimplemented: TerminalSession.windowChanged")
  72. def eofReceived(self):
  73. # ISession.eofReceived
  74. raise NotImplementedError("Unimplemented: TerminalSession.eofReceived")
  75. def closed(self):
  76. # ISession.closed
  77. pass
  78. class TerminalUser(avatar.ConchUser, components.Adapter):
  79. def __init__(self, original, avatarId):
  80. components.Adapter.__init__(self, original)
  81. avatar.ConchUser.__init__(self)
  82. self.channelLookup[b"session"] = session.SSHSession
  83. class TerminalRealm:
  84. userFactory = TerminalUser
  85. sessionFactory = TerminalSession
  86. transportFactory = TerminalSessionTransport
  87. chainedProtocolFactory = insults.ServerProtocol
  88. def _getAvatar(self, avatarId):
  89. comp = components.Componentized()
  90. user = self.userFactory(comp, avatarId)
  91. sess = self.sessionFactory(comp)
  92. sess.transportFactory = self.transportFactory
  93. sess.chainedProtocolFactory = self.chainedProtocolFactory
  94. comp.setComponent(iconch.IConchUser, user)
  95. comp.setComponent(iconch.ISession, sess)
  96. return user
  97. def __init__(self, transportFactory=None):
  98. if transportFactory is not None:
  99. self.transportFactory = transportFactory
  100. def requestAvatar(self, avatarId, mind, *interfaces):
  101. for i in interfaces:
  102. if i is iconch.IConchUser:
  103. return (iconch.IConchUser, self._getAvatar(avatarId), lambda: None)
  104. raise NotImplementedError()
  105. class ConchFactory(factory.SSHFactory):
  106. publicKeys: Dict[bytes, bytes] = {}
  107. privateKeys: Dict[bytes, bytes] = {}
  108. def __init__(self, portal):
  109. self.portal = portal