mirror of
https://github.com/naruxde/revpicommander.git
synced 2025-11-08 16:43:53 +01:00
feat: SSH tunnel server extended to execute commands on remote host.
Signed-off-by: Sven Sager <akira@narux.de>
This commit is contained in:
@@ -10,14 +10,18 @@ __copyright__ = "Copyright (C) 2023 Sven Sager"
|
|||||||
__license__ = "GPLv2"
|
__license__ = "GPLv2"
|
||||||
|
|
||||||
import select
|
import select
|
||||||
|
from logging import getLogger
|
||||||
from socketserver import BaseRequestHandler, ThreadingTCPServer
|
from socketserver import BaseRequestHandler, ThreadingTCPServer
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
from typing import Tuple, Union
|
||||||
|
|
||||||
from paramiko.client import MissingHostKeyPolicy, SSHClient
|
from paramiko.client import MissingHostKeyPolicy, SSHClient
|
||||||
from paramiko.rsakey import RSAKey
|
from paramiko.rsakey import RSAKey
|
||||||
from paramiko.ssh_exception import PasswordRequiredException
|
from paramiko.ssh_exception import PasswordRequiredException
|
||||||
from paramiko.transport import Transport
|
from paramiko.transport import Transport
|
||||||
|
|
||||||
|
log = getLogger("ssh_tunneling")
|
||||||
|
|
||||||
|
|
||||||
class ForwardServer(ThreadingTCPServer):
|
class ForwardServer(ThreadingTCPServer):
|
||||||
daemon_threads = True
|
daemon_threads = True
|
||||||
@@ -33,10 +37,14 @@ class Handler(BaseRequestHandler):
|
|||||||
self.request.getpeername(),
|
self.request.getpeername(),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
log.error(e)
|
||||||
return
|
return
|
||||||
if chan is None:
|
if chan is None:
|
||||||
|
log.error("Could not create a ssh channel")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
log.info("Starting tunnel exchange loop")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
r, w, x = select.select([self.request, chan], [], [], 5.0)
|
r, w, x = select.select([self.request, chan], [], [], 5.0)
|
||||||
if self.request in r:
|
if self.request in r:
|
||||||
@@ -50,6 +58,8 @@ class Handler(BaseRequestHandler):
|
|||||||
break
|
break
|
||||||
self.request.send(data)
|
self.request.send(data)
|
||||||
|
|
||||||
|
log.info("Stopped tunnel exchange loop")
|
||||||
|
|
||||||
chan.close()
|
chan.close()
|
||||||
self.request.close()
|
self.request.close()
|
||||||
|
|
||||||
@@ -162,6 +172,30 @@ class SSHLocalTunnel:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def send_cmd(self, cmd: str, timeout: float = None) -> Union[Tuple[str, str], Tuple[None, None]]:
|
||||||
|
"""
|
||||||
|
Send simple command to ssh host.
|
||||||
|
|
||||||
|
The output of stdout and stderr is returned as a tuple of two elements.
|
||||||
|
This elements could be None, in case of an internal error.
|
||||||
|
|
||||||
|
:param cmd: Shell command to execute on remote host
|
||||||
|
:param timeout: Timeout for execution
|
||||||
|
:return: Tuple with stdout and stderr
|
||||||
|
"""
|
||||||
|
if not self._th_server.is_alive():
|
||||||
|
raise RuntimeError("Not connected")
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, stdout, stderr = self._ssh_client.exec_command(cmd, 1024, timeout)
|
||||||
|
buffer_out = stdout.read()
|
||||||
|
buffer_err = stderr.read()
|
||||||
|
|
||||||
|
return buffer_out.decode(), buffer_err.decode()
|
||||||
|
except Exception as e:
|
||||||
|
log.error(e)
|
||||||
|
return None, None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def connected(self):
|
def connected(self):
|
||||||
"""Check connection state of ssh tunnel."""
|
"""Check connection state of ssh tunnel."""
|
||||||
|
|||||||
Reference in New Issue
Block a user