This commit is contained in:
Mark Qvist
2026-07-26 14:29:47 +02:00
parent e3f1a5e7cd
commit 64fee86ebc
5 changed files with 30 additions and 44 deletions
+6
View File
@@ -35,7 +35,13 @@
from ._version import __version__ from ._version import __version__
import os import os
import glob
module_abs_filename = os.path.abspath(__file__) module_abs_filename = os.path.abspath(__file__)
module_dir = os.path.dirname(module_abs_filename) module_dir = os.path.dirname(module_abs_filename)
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
modules = py_modules+pyc_modules
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
def _get_version(): return __version__ def _get_version(): return __version__
+20 -41
View File
@@ -71,7 +71,6 @@ _retry_timer: retry.RetryThread | None = None
_destination: RNS.Destination | None = None _destination: RNS.Destination | None = None
_loop: asyncio.AbstractEventLoop | None = None _loop: asyncio.AbstractEventLoop | None = None
async def _check_finished(timeout: float = 0): async def _check_finished(timeout: float = 0):
return _finished is not None and await process.event_wait(_finished, timeout=timeout) return _finished is not None and await process.event_wait(_finished, timeout=timeout)
@@ -92,13 +91,11 @@ async def _spin_tty(until=None, msg=None, timeout=None):
print(("\b\b"+syms[i]+" "), end="") print(("\b\b"+syms[i]+" "), end="")
sys.stdout.flush() sys.stdout.flush()
i = (i+1)%len(syms) i = (i+1)%len(syms)
print("\r"+" "*len(msg)+" \r", end="") print("\r"+" "*len(msg)+" \r", end="")
if timeout != None and time.time() > timeout: return False if timeout != None and time.time() > timeout: return False
else: return True else: return True
async def _spin_pipe(until: callable = None, msg=None, timeout: float | None = None) -> bool: async def _spin_pipe(until: callable = None, msg=None, timeout: float | None = None) -> bool:
if timeout is not None: timeout += time.time() if timeout is not None: timeout += time.time()
@@ -136,14 +133,11 @@ def compute_target_rns_loglevel(verbosity: int, quietness: int, base_level: int
if target < RNS.LOG_NONE: target = RNS.LOG_NONE if target < RNS.LOG_NONE: target = RNS.LOG_NONE
if target > RNS.LOG_EXTREME: target = RNS.LOG_EXTREME if target > RNS.LOG_EXTREME: target = RNS.LOG_EXTREME
return target return target
except Exception: return base_level except Exception: return base_level
class RemoteExecutionError(Exception): class RemoteExecutionError(Exception):
def __init__(self, msg): self.msg = msg def __init__(self, msg): self.msg = msg
async def _initiate_link(configdir, rnsconfigdir, identitypath=None, logfile=None, verbosity=0, quietness=0, noid=False, destination=None, async def _initiate_link(configdir, rnsconfigdir, identitypath=None, logfile=None, verbosity=0, quietness=0, noid=False, destination=None,
timeout=RNS.Transport.PATH_REQUEST_TIMEOUT): timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
global _identity, _reticulum, _link, _destination, _remote_exec_grace global _identity, _reticulum, _link, _destination, _remote_exec_grace
@@ -153,13 +147,12 @@ async def _initiate_link(configdir, rnsconfigdir, identitypath=None, logfile=Non
raise RemoteExecutionError( raise RemoteExecutionError(
"Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format( "Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(
hex=dest_len, byte=dest_len // 2)) hex=dest_len, byte=dest_len // 2))
try:
destination_hash = bytes.fromhex(destination) try: destination_hash = bytes.fromhex(destination)
except Exception as e: except Exception as e: raise RemoteExecutionError("Invalid destination entered. Check your input.")
raise RemoteExecutionError("Invalid destination entered. Check your input.")
if _reticulum is None: if _reticulum is None:
targetloglevel = compute_target_rns_loglevel(verbosity, quietness, RNS.LOG_ERROR) targetloglevel = compute_target_rns_loglevel(verbosity, quietness, RNS.LOG_INFO)
RNS.logfile = logfile RNS.logfile = logfile
_reticulum = RNS.Reticulum(configdir=rnsconfigdir, loglevel=targetloglevel, logdest=RNS.LOG_FILE) _reticulum = RNS.Reticulum(configdir=rnsconfigdir, loglevel=targetloglevel, logdest=RNS.LOG_FILE)
@@ -168,19 +161,14 @@ async def _initiate_link(configdir, rnsconfigdir, identitypath=None, logfile=Non
if not RNS.Transport.has_path(destination_hash): if not RNS.Transport.has_path(destination_hash):
RNS.Transport.request_path(destination_hash) RNS.Transport.request_path(destination_hash)
RNS.log(f"Requesting path...", RNS.LOG_INFO) RNS.log(f"Requesting path to {RNS.prettyhexrep(destination_hash)}", RNS.LOG_INFO)
if not await _spin(until=lambda: RNS.Transport.has_path(destination_hash), msg="Requesting path...", if not await _spin(until=lambda: RNS.Transport.has_path(destination_hash), msg=f"Requesting path to {RNS.prettyhexrep(destination_hash)}",
timeout=timeout, quiet=quietness > 0): timeout=timeout, quiet=quietness > 0):
raise RemoteExecutionError("Path not found") raise RemoteExecutionError("Path not found")
if _destination is None: if _destination is None:
listener_identity = RNS.Identity.recall(destination_hash) listener_identity = RNS.Identity.recall(destination_hash)
_destination = RNS.Destination( _destination = RNS.Destination(listener_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, rnsh.APP_NAME)
listener_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
rnsh.APP_NAME
)
if _link is None or _link.status == RNS.Link.PENDING: if _link is None or _link.status == RNS.Link.PENDING:
RNS.log("No link", RNS.LOG_DEBUG) RNS.log("No link", RNS.LOG_DEBUG)
@@ -189,34 +177,30 @@ async def _initiate_link(configdir, rnsconfigdir, identitypath=None, logfile=Non
_link.set_link_closed_callback(_client_link_closed) _link.set_link_closed_callback(_client_link_closed)
RNS.log(f"Establishing link...", RNS.LOG_VERBOSE) RNS.log(f"Establishing link with {RNS.prettyhexrep(destination_hash)}", RNS.LOG_INFO)
if not await _spin(until=lambda: _link.status == RNS.Link.ACTIVE, msg="Establishing link...", if not await _spin(until=lambda: _link.status == RNS.Link.ACTIVE, msg=f"Establishing link with {RNS.prettyhexrep(destination_hash)}",
timeout=timeout, quiet=quietness > 0): timeout=timeout, quiet=quietness > 0):
raise RemoteExecutionError("Could not establish link with " + RNS.prettyhexrep(destination_hash)) raise RemoteExecutionError("Could not establish link with " + RNS.prettyhexrep(destination_hash))
RNS.log("Have link", RNS.LOG_DEBUG) RNS.log(f"Link established with {RNS.prettyhexrep(destination_hash)}", RNS.LOG_INFO)
if not noid and not _link.did_identify: if not noid and not _link.did_identify:
# Delay a tiny bit to allow listener to fully enter WAIT_IDENT state # Delay a tiny bit to allow listener to fully enter WAIT_IDENT state
await asyncio.sleep(min(1, _link.rtt * 1.1 + 0.05)) await asyncio.sleep(min(1, _link.rtt * 1.1 + 0.05))
_link.identify(_identity) _link.identify(_identity)
_link.did_identify = True _link.did_identify = True
async def _handle_error(errmsg: RNS.MessageBase): async def _handle_error(errmsg: RNS.MessageBase):
if isinstance(errmsg, protocol.ErrorMessage): if isinstance(errmsg, protocol.ErrorMessage):
with contextlib.suppress(Exception): with contextlib.suppress(Exception):
if _link and _link.status == RNS.Link.ACTIVE: if _link and _link.status == RNS.Link.ACTIVE: _link.teardown()
_link.teardown()
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
raise RemoteExecutionError(f"Remote error: {errmsg.msg}") raise RemoteExecutionError(f"Remote error: {errmsg.msg}")
async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, logfile:str, verbosity: int, quietness: int, noid: bool, destination: str, async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, logfile:str, verbosity: int, quietness: int, noid: bool, destination: str,
timeout: float, command: [str] | None = None): timeout: float, command: [str] | None = None):
global _finished, _link global _finished, _link
if timeout is None: if timeout is None: timeout = RNS.Transport.PATH_REQUEST_TIMEOUT
timeout = RNS.Transport.PATH_REQUEST_TIMEOUT
with process.TTYRestorer(sys.stdin.fileno()) as ttyRestorer: with process.TTYRestorer(sys.stdin.fileno()) as ttyRestorer:
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
state = InitiatorState.IS_INITIAL state = InitiatorState.IS_INITIAL
@@ -233,8 +217,7 @@ async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, logfile:
destination=destination, destination=destination,
timeout=timeout) timeout=timeout)
if not _link or _link.status not in [RNS.Link.ACTIVE, RNS.Link.PENDING]: if not _link or _link.status not in [RNS.Link.ACTIVE, RNS.Link.PENDING]: return 255
return 255
state = InitiatorState.IS_LINKED state = InitiatorState.IS_LINKED
outlet = session.RNSOutlet(_link) outlet = session.RNSOutlet(_link)
@@ -245,9 +228,8 @@ async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, logfile:
try: try:
vm = _pq.get(timeout=max(outlet.rtt * 20, 5)) vm = _pq.get(timeout=max(outlet.rtt * 20, 5))
await _handle_error(vm) await _handle_error(vm)
if not isinstance(vm, protocol.VersionInfoMessage): if not isinstance(vm, protocol.VersionInfoMessage): raise Exception("Invalid message received")
raise Exception("Invalid message received") RNS.log(f"Connected server version info: sw {vm.sw_version}, proto {vm.protocol_version}", RNS.LOG_INFO)
RNS.log(f"Server version info: sw {vm.sw_version} prot {vm.protocol_version}", RNS.LOG_DEBUG)
state = InitiatorState.IS_RUNNING state = InitiatorState.IS_RUNNING
except queue.Empty: except queue.Empty:
print("Protocol error") print("Protocol error")
@@ -323,8 +305,7 @@ async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, logfile:
pre_esc = False pre_esc = False
data.append(b) data.append(b)
if not line_mode: if not line_mode: data_buffer.extend(data)
data_buffer.extend(data)
else: else:
line_buffer.extend(data) line_buffer.extend(data)
if line_flush: if line_flush:
@@ -338,8 +319,7 @@ async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, logfile:
blind_write_count += len(data) blind_write_count += len(data)
except EOFError: except EOFError:
if os.isatty(0): if os.isatty(0): data_buffer.extend(process.CTRL_D)
data_buffer.extend(process.CTRL_D)
stdin_eof = True stdin_eof = True
process.tty_unset_reader_callbacks(sys.stdin.fileno()) process.tty_unset_reader_callbacks(sys.stdin.fileno())
@@ -414,8 +394,7 @@ async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, logfile:
_link.teardown() _link.teardown()
return 200 return 200
except queue.Empty: except queue.Empty: processed = False
processed = False
if channel.is_ready_to_send(): if channel.is_ready_to_send():
def compress_adaptive(buf: bytes): def compress_adaptive(buf: bytes):
@@ -437,8 +416,7 @@ async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, logfile:
if compressed_length < max_data_len and compressed_length < chunk_segment_length: if compressed_length < max_data_len and compressed_length < chunk_segment_length:
comp_success = True comp_success = True
break break
else: else: comp_try += 1
comp_try += 1
if comp_success: if comp_success:
diff = max_data_len - len(compressed_chunk) diff = max_data_len - len(compressed_chunk)
@@ -467,6 +445,7 @@ async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, logfile:
r, c, h, v = process.tty_get_winsize(0) r, c, h, v = process.tty_get_winsize(0)
channel.send(protocol.WindowSizeMessage(r, c, h, v)) channel.send(protocol.WindowSizeMessage(r, c, h, v))
processed = True processed = True
except RemoteExecutionError as e: except RemoteExecutionError as e:
print(e.msg) print(e.msg)
return 255 return 255
+1 -2
View File
@@ -161,8 +161,7 @@ async def listen(configdir, rnsconfigdir, command, identitypath=None, logfile=No
_no_remote_command = no_remote_command _no_remote_command = no_remote_command
session.ListenerSession.allow_remote_command = not no_remote_command session.ListenerSession.allow_remote_command = not no_remote_command
_remote_cmd_as_args = remote_cmd_as_args _remote_cmd_as_args = remote_cmd_as_args
if (_cmd is None or len(_cmd) == 0 or _cmd[0] is None or len(_cmd[0]) == 0) \ if (_cmd is None or len(_cmd) == 0 or _cmd[0] is None or len(_cmd[0]) == 0) and (_no_remote_command or _remote_cmd_as_args):
and (_no_remote_command or _remote_cmd_as_args):
raise Exception(f"Unable to look up shell for {os.getlogin}, cannot proceed with -A or -C and no <program>.") raise Exception(f"Unable to look up shell for {os.getlogin}, cannot proceed with -A or -C and no <program>.")
session.ListenerSession.default_command = _cmd session.ListenerSession.default_command = _cmd
+1
View File
@@ -163,6 +163,7 @@ async def _rnsh_cli_main():
timeout=args.timeout, timeout=args.timeout,
command=args.command command=args.command
) )
RNS.log(f"Sesssion with <{args.destination}> ended", RNS.LOG_INFO)
return return_code if args.mirror else 0 return return_code if args.mirror else 0
else: else:
print("") print("")
+2 -1
View File
@@ -309,7 +309,8 @@ class ListenerSession:
self.terminate("Invalid state") self.terminate("Invalid state")
return return
self.cmdline = self.default_command self.cmdline = []
self.cmdline.extend(self.default_command)
if not self.allow_remote_command and cmdline and len(cmdline) > 0: if not self.allow_remote_command and cmdline and len(cmdline) > 0:
self.terminate("Remote command line not allowed by listener") self.terminate("Remote command line not allowed by listener")
return return