Remote command append mode (-A) for listener

This commit is contained in:
Aaron Heise 2023-02-14 06:18:22 -06:00
parent 21b80819c6
commit d4cb31e220
4 changed files with 88 additions and 66 deletions

View File

@ -24,6 +24,12 @@ There will sometimes be breaking changes in the protocol between
releases. Use at your own peril!
## Recent Changes
### v0.0.7
Added `-A` command line option. This listener option causes the
remote command line to be appended to the arguments list of the
launched program. This allows the listener to jail connections
to a particular executable while still allowing parameters.
### v0.0.6
Minor improvements in transport efficiency
@ -92,7 +98,7 @@ Usage:
rnsh [--config <configdir>] [-i <identityfile>] [-s <service_name>] [-l] -p
rnsh -l [--config <configfile>] [-i <identityfile>] [-s <service_name>]
[-v... | -q...] [-b <period>] (-n | -a <identity_hash> [-a <identity_hash>] ...)
[-C] [[--] <program> [<arg> ...]]
[-A | -C] [[--] <program> [<arg> ...]]
rnsh [--config <configfile>] [-i <identityfile>] [-s <service_name>]
[-v... | -q...] [-N] [-m] [-w <timeout>] <destination_hash>
[[--] <program> [<arg> ...]]
@ -100,33 +106,34 @@ Usage:
rnsh --version
Options:
--config DIR Alternate Reticulum config directory to use
-i FILE --identity FILE Specific identity file to use
-s NAME --service NAME Listen on/connect to specific service name if not default
-p --print-identity Print identity information and exit
-l --listen Listen (server) mode. If supplied, <program> <arg>...will
be used as the command line when the initiator does not
provide one or when remote command is disabled. If
<program> is not supplied, the default shell of the
user rnsh is running under will be used.
-b --announce PERIOD Announce on startup and every PERIOD seconds
Specify 0 for PERIOD to announce on startup only.
-a HASH --allowed HASH Specify identities allowed to connect
-n --no-auth Disable authentication
-N --no-id Disable identify on connect
-C --no-remote-command Disable executing command line from remote
-m --mirror Client returns with code of remote process
-w TIME --timeout TIME Specify client connect and request timeout in seconds
-q --quiet Increase quietness (move level up), multiple increases effect
DEFAULT LOGGING LEVEL
CRITICAL (silent)
Initiator -> ERROR
WARNING
Listener -> INFO
DEBUG (insane)
-v --verbose Increase verbosity (move level down), multiple increases effect
--version Show version
-h --help Show this help
--config DIR Alternate Reticulum config directory to use
-i FILE --identity FILE Specific identity file to use
-s NAME --service NAME Listen on/connect to specific service name if not default
-p --print-identity Print identity information and exit
-l --listen Listen (server) mode. If supplied, <program> <arg>...will
be used as the command line when the initiator does not
provide one or when remote command is disabled. If
<program> is not supplied, the default shell of the
user rnsh is running under will be used.
-b --announce PERIOD Announce on startup and every PERIOD seconds
Specify 0 for PERIOD to announce on startup only.
-a HASH --allowed HASH Specify identities allowed to connect
-n --no-auth Disable authentication
-N --no-id Disable identify on connect
-A --remote-command-as-args Concatenate remote command to argument list of <program>/shell
-C --no-remote-command Disable executing command line from remote
-m --mirror Client returns with code of remote process
-w TIME --timeout TIME Specify client connect and request timeout in seconds
-q --quiet Increase quietness (move level up), multiple increases effect
DEFAULT LOGGING LEVEL
CRITICAL (silent)
Initiator -> ERROR
WARNING
Listener -> INFO
DEBUG (insane)
-v --verbose Increase verbosity (move level down), multiple increases effect
--version Show version
-h --help Show this help
```
## How it works

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "rnsh"
version = "0.0.6"
version = "0.0.7"
description = "Shell over Reticulum"
authors = ["acehoss <acehoss@acehoss.net>"]
license = "MIT"

View File

@ -21,7 +21,7 @@ Usage:
rnsh [--config <configdir>] [-i <identityfile>] [-s <service_name>] [-l] -p
rnsh -l [--config <configfile>] [-i <identityfile>] [-s <service_name>]
[-v... | -q...] [-b <period>] (-n | -a <identity_hash> [-a <identity_hash>] ...)
[-C] [[--] <program> [<arg> ...]]
[-A | -C] [[--] <program> [<arg> ...]]
rnsh [--config <configfile>] [-i <identityfile>] [-s <service_name>]
[-v... | -q...] [-N] [-m] [-w <timeout>] <destination_hash>
[[--] <program> [<arg> ...]]
@ -29,33 +29,34 @@ Usage:
rnsh --version
Options:
--config DIR Alternate Reticulum config directory to use
-i FILE --identity FILE Specific identity file to use
-s NAME --service NAME Listen on/connect to specific service name if not default
-p --print-identity Print identity information and exit
-l --listen Listen (server) mode. If supplied, <program> <arg>...will
be used as the command line when the initiator does not
provide one or when remote command is disabled. If
<program> is not supplied, the default shell of the
user rnsh is running under will be used.
-b --announce PERIOD Announce on startup and every PERIOD seconds
Specify 0 for PERIOD to announce on startup only.
-a HASH --allowed HASH Specify identities allowed to connect
-n --no-auth Disable authentication
-N --no-id Disable identify on connect
-C --no-remote-command Disable executing command line from remote
-m --mirror Client returns with code of remote process
-w TIME --timeout TIME Specify client connect and request timeout in seconds
-q --quiet Increase quietness (move level up), multiple increases effect
DEFAULT LOGGING LEVEL
CRITICAL (silent)
Initiator -> ERROR
WARNING
Listener -> INFO
DEBUG (insane)
-v --verbose Increase verbosity (move level down), multiple increases effect
--version Show version
-h --help Show this help
--config DIR Alternate Reticulum config directory to use
-i FILE --identity FILE Specific identity file to use
-s NAME --service NAME Listen on/connect to specific service name if not default
-p --print-identity Print identity information and exit
-l --listen Listen (server) mode. If supplied, <program> <arg>...will
be used as the command line when the initiator does not
provide one or when remote command is disabled. If
<program> is not supplied, the default shell of the
user rnsh is running under will be used.
-b --announce PERIOD Announce on startup and every PERIOD seconds
Specify 0 for PERIOD to announce on startup only.
-a HASH --allowed HASH Specify identities allowed to connect
-n --no-auth Disable authentication
-N --no-id Disable identify on connect
-A --remote-command-as-args Concatenate remote command to argument list of <program>/shell
-C --no-remote-command Disable executing command line from remote
-m --mirror Client returns with code of remote process
-w TIME --timeout TIME Specify client connect and request timeout in seconds
-q --quiet Increase quietness (move level up), multiple increases effect
DEFAULT LOGGING LEVEL
CRITICAL (silent)
Initiator -> ERROR
WARNING
Listener -> INFO
DEBUG (insane)
-v --verbose Increase verbosity (move level down), multiple increases effect
--version Show version
-h --help Show this help
'''
@ -64,7 +65,8 @@ class Args:
global usage
try:
argv, program_args = _split_array_at(argv, "--")
if len(program_args) > 0:
# need to add first arg after -- back onto argv for docopts, but only for listener
if len(program_args) > 0 and next(filter(lambda a: a == "-l" or a == "--listen", argv), None) is not None:
argv.append(program_args[0])
self.program_args = program_args[1:]
@ -88,6 +90,7 @@ class Args:
self.valid = False
self.no_auth = args.get("--no-auth", None) or False
self.allowed = args.get("--allowed", None) or []
self.remote_cmd_as_args = args.get("--remote-command-as-args", None) or False
self.no_remote_cmd = args.get("--no-remote-command", None) or False
self.program = args.get("<program>", None)
self.program_args = args.get("<arg>", None) or []

View File

@ -69,6 +69,7 @@ _retry_timer: retry.RetryThread | None = None
_destination: RNS.Destination | None = None
_loop: asyncio.AbstractEventLoop | None = None
_no_remote_command = True
_remote_cmd_as_args = False
async def _check_finished(timeout: float = 0):
@ -115,8 +116,9 @@ def _print_identity(configdir, identitypath, service_name, include_destination:
async def _listen(configdir, command, identitypath=None, service_name="default", verbosity=0, quietness=0, allowed=None,
disable_auth=None, announce_period=900, no_remote_command=True):
disable_auth=None, announce_period=900, no_remote_command=True, remote_cmd_as_args=False):
global _identity, _allow_all, _allowed_identity_hashes, _reticulum, _cmd, _destination, _no_remote_command
global _remote_cmd_as_args
log = _get_logger("_listen")
@ -128,15 +130,21 @@ async def _listen(configdir, command, identitypath=None, service_name="default",
_cmd = command
if _cmd is None or len(_cmd) == 0:
shell = pwd.getpwuid(os.getuid()).pw_shell
shell = None
try:
shell = pwd.getpwuid(os.getuid()).pw_shell
except Exception as e:
log.error(f"Error looking up shell: {e}")
log.info(f"Using {shell} for default command.")
_cmd = [shell]
_cmd = [shell] if shell else None
else:
log.info(f"Using command {shlex.join(_cmd)}")
_no_remote_command = no_remote_command
if _cmd is None and _no_remote_command:
raise Exception(f"Unable to look up shell for {os.getlogin}, cannot proceed with -C and no <program>.")
_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) \
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>.")
if disable_auth:
_allow_all = True
@ -603,13 +611,16 @@ def _listen_request(path, data, request_id, link_id, remote_identity, requested_
if not remote_version <= _PROTOCOL_VERSION_DEFAULT:
return Session.error_response("Listener<->initiator version mismatch")
cmd = _cmd
cmd = _cmd.copy()
if remote_version >= _PROTOCOL_VERSION_1:
remote_command = data[Session.REQUEST_IDX_CMD]
if remote_command is not None and len(remote_command) > 0:
if _no_remote_command:
return Session.error_response("Listener does not permit initiator to provide command.")
cmd = remote_command
elif _remote_cmd_as_args:
cmd.extend(remote_command)
else:
cmd = remote_command
if not _no_remote_command and (cmd is None or len(cmd) == 0):
return Session.error_response("No command supplied and no default command available.")
@ -939,7 +950,8 @@ async def _rnsh_cli_main():
allowed=args.allowed,
disable_auth=args.no_auth,
announce_period=args.announce,
no_remote_command=args.no_remote_cmd)
no_remote_command=args.no_remote_cmd,
remote_cmd_as_args=args.remote_cmd_as_args)
return 0
if args.destination is not None and args.service_name is not None: