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.

121 lines
4.3 KiB

7 years ago
  1. # Copyright (C) 2012 Yipit, Inc <coders@yipit.com>
  2. #
  3. # This file is part of paramiko.
  4. #
  5. # Paramiko is free software; you can redistribute it and/or modify it under the
  6. # terms of the GNU Lesser General Public License as published by the Free
  7. # Software Foundation; either version 2.1 of the License, or (at your option)
  8. # any later version.
  9. #
  10. # Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
  11. # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  13. # details.
  14. #
  15. # You should have received a copy of the GNU Lesser General Public License
  16. # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
  17. # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
  18. import os
  19. from shlex import split as shlsplit
  20. import signal
  21. from select import select
  22. import socket
  23. import time
  24. from paramiko.ssh_exception import ProxyCommandFailure
  25. from paramiko.util import ClosingContextManager
  26. class ProxyCommand(ClosingContextManager):
  27. """
  28. Wraps a subprocess running ProxyCommand-driven programs.
  29. This class implements a the socket-like interface needed by the
  30. `.Transport` and `.Packetizer` classes. Using this class instead of a
  31. regular socket makes it possible to talk with a Popen'd command that will
  32. proxy traffic between the client and a server hosted in another machine.
  33. Instances of this class may be used as context managers.
  34. """
  35. def __init__(self, command_line):
  36. """
  37. Create a new CommandProxy instance. The instance created by this
  38. class can be passed as an argument to the `.Transport` class.
  39. :param str command_line:
  40. the command that should be executed and used as the proxy.
  41. """
  42. # NOTE: subprocess import done lazily so platforms without it (e.g.
  43. # GAE) can still import us during overall Paramiko load.
  44. from subprocess import Popen, PIPE
  45. self.cmd = shlsplit(command_line)
  46. self.process = Popen(self.cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE,
  47. bufsize=0)
  48. self.timeout = None
  49. def send(self, content):
  50. """
  51. Write the content received from the SSH client to the standard
  52. input of the forked command.
  53. :param str content: string to be sent to the forked command
  54. """
  55. try:
  56. self.process.stdin.write(content)
  57. except IOError as e:
  58. # There was a problem with the child process. It probably
  59. # died and we can't proceed. The best option here is to
  60. # raise an exception informing the user that the informed
  61. # ProxyCommand is not working.
  62. raise ProxyCommandFailure(' '.join(self.cmd), e.strerror)
  63. return len(content)
  64. def recv(self, size):
  65. """
  66. Read from the standard output of the forked program.
  67. :param int size: how many chars should be read
  68. :return: the string of bytes read, which may be shorter than requested
  69. """
  70. try:
  71. buffer = b''
  72. start = time.time()
  73. while len(buffer) < size:
  74. select_timeout = None
  75. if self.timeout is not None:
  76. elapsed = (time.time() - start)
  77. if elapsed >= self.timeout:
  78. raise socket.timeout()
  79. select_timeout = self.timeout - elapsed
  80. r, w, x = select(
  81. [self.process.stdout], [], [], select_timeout)
  82. if r and r[0] == self.process.stdout:
  83. buffer += os.read(
  84. self.process.stdout.fileno(), size - len(buffer))
  85. return buffer
  86. except socket.timeout:
  87. if buffer:
  88. # Don't raise socket.timeout, return partial result instead
  89. return buffer
  90. raise # socket.timeout is a subclass of IOError
  91. except IOError as e:
  92. raise ProxyCommandFailure(' '.join(self.cmd), e.strerror)
  93. def close(self):
  94. os.kill(self.process.pid, signal.SIGTERM)
  95. @property
  96. def closed(self):
  97. return self.process.returncode is not None
  98. @property
  99. def _closed(self):
  100. # Concession to Python 3 socket-like API
  101. return self.closed
  102. def settimeout(self, timeout):
  103. self.timeout = timeout

Powered by TurnKey Linux.