From bac2a6b0c7945241f493e67e9329a13378daee7e Mon Sep 17 00:00:00 2001 From: nom3ad <19239479+nom3ad@users.noreply.github.com> Date: Tue, 16 Jul 2024 23:38:36 +0530 Subject: [PATCH] windows: add --remote-shell option to select cmd/powershell --- docs/manpage.rst | 5 +++ sshuttle/client.py | 11 ++--- sshuttle/cmdline.py | 1 + sshuttle/options.py | 8 ++++ sshuttle/ssh.py | 101 +++++++++++++++++++++++--------------------- 5 files changed, 74 insertions(+), 52 deletions(-) diff --git a/docs/manpage.rst b/docs/manpage.rst index b860dde..49022ec 100644 --- a/docs/manpage.rst +++ b/docs/manpage.rst @@ -181,6 +181,11 @@ Options in a non-standard location or you want to provide extra options to the ssh command, for example, ``-e 'ssh -v'``. +.. option:: --remote-shell + + For Windows targets, specify configured remote shell program alternative to defacto posix shell. + It would be either ``cmd`` or ``powershell`` unless something like git-bash is in use. + .. option:: --no-cmd-delimiter Do not add a double dash (--) delimiter before invoking Python on diff --git a/sshuttle/client.py b/sshuttle/client.py index 8271cb9..08df678 100644 --- a/sshuttle/client.py +++ b/sshuttle/client.py @@ -211,8 +211,8 @@ class FirewallClient: self.auto_nets = [] argv0 = sys.argv[0] - # argv0 is either be a normal python file or an executable. - # After installed as a package, sshuttle command points to an .exe in Windows and python shebang script elsewhere. + # argv0 is either be a normal Python file or an executable. + # After installed as a package, sshuttle command points to an .exe in Windows and Python shebang script elsewhere. argvbase = (([sys.executable, sys.argv[0]] if argv0.endswith('.py') else [argv0]) + ['-v'] * (helpers.verbose or 0) + ['--method', method_name] + @@ -591,7 +591,7 @@ def ondns(listener, method, mux, handlers): def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, python, latency_control, latency_buffer_size, dns_listener, seed_hosts, auto_hosts, auto_nets, daemon, - to_nameserver, add_cmd_delimiter): + to_nameserver, add_cmd_delimiter, remote_shell): helpers.logprefix = 'c : ' debug1('Starting client with Python version %s' @@ -607,6 +607,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, ssh_cmd, remotename, python, stderr=ssyslog._p and ssyslog._p.stdin, add_cmd_delimiter=add_cmd_delimiter, + remote_shell=remote_shell, options=dict(latency_control=latency_control, latency_buffer_size=latency_buffer_size, auto_hosts=auto_hosts, @@ -809,7 +810,7 @@ def main(listenip_v6, listenip_v4, latency_buffer_size, dns, nslist, method_name, seed_hosts, auto_hosts, auto_nets, subnets_include, subnets_exclude, daemon, to_nameserver, pidfile, - user, group, sudo_pythonpath, add_cmd_delimiter, tmark): + user, group, sudo_pythonpath, add_cmd_delimiter, remote_shell, tmark): if not remotename: raise Fatal("You must use -r/--remote to specify a remote " @@ -1158,7 +1159,7 @@ def main(listenip_v6, listenip_v4, return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, python, latency_control, latency_buffer_size, dns_listener, seed_hosts, auto_hosts, auto_nets, - daemon, to_nameserver, add_cmd_delimiter) + daemon, to_nameserver, add_cmd_delimiter, remote_shell) finally: try: if daemon: diff --git a/sshuttle/cmdline.py b/sshuttle/cmdline.py index 3e98eee..11d6796 100644 --- a/sshuttle/cmdline.py +++ b/sshuttle/cmdline.py @@ -116,6 +116,7 @@ def main(): opt.group, opt.sudo_pythonpath, opt.add_cmd_delimiter, + opt.remote_shell, opt.tmark) if return_code == 0: diff --git a/sshuttle/options.py b/sshuttle/options.py index ac5a96d..b610a11 100644 --- a/sshuttle/options.py +++ b/sshuttle/options.py @@ -315,6 +315,14 @@ parser.add_argument( do not add a double dash before the python command """ ) +parser.add_argument( + "--remote-shell", + metavar="PROGRAM", + help=""" + alternate remote shell program instead of defacto posix shell. + For Windows targets it would be either `cmd` or `powershell` unless something like git-bash is in use. + """ +) parser.add_argument( "--seed-hosts", metavar="HOSTNAME[,HOSTNAME]", diff --git a/sshuttle/ssh.py b/sshuttle/ssh.py index d7967c5..8f295e2 100644 --- a/sshuttle/ssh.py +++ b/sshuttle/ssh.py @@ -84,7 +84,7 @@ def parse_hostport(rhostport): return username, password, port, host -def connect(ssh_cmd, rhostport, python, stderr, add_cmd_delimiter, options): +def connect(ssh_cmd, rhostport, python, stderr, add_cmd_delimiter, remote_shell, options): username, password, port, host = parse_hostport(rhostport) if username: rhost = "{}@{}".format(username, host) @@ -134,52 +134,59 @@ def connect(ssh_cmd, rhostport, python, stderr, add_cmd_delimiter, options): portl = ["-p", str(port)] else: portl = [] - if python: - pycmd = '"%s" -c "%s"' % (python, pyscript) - else: - # By default, we run the following code in a shell. - # However, with restricted shells and other unusual - # situations, there can be trouble. See the RESTRICTED - # SHELL section in "man bash" for more information. The - # code makes many assumptions: - # - # (1) That /bin/sh exists and that we can call it. - # Restricted shells often do *not* allow you to run - # programs specified with an absolute path like /bin/sh. - # Either way, if there is trouble with this, it should - # return error code 127. - # - # (2) python3 or python exists in the PATH and is - # executable. If they aren't, then exec won't work (see (4) - # below). - # - # (3) In /bin/sh, that we can redirect stderr in order to - # hide the version that "python3 -V" might print (some - # restricted shells don't allow redirection, see - # RESTRICTED SHELL section in 'man bash'). However, if we - # are in a restricted shell, we'd likely have trouble with - # assumption (1) above. - # - # (4) The 'exec' command should work except if we failed - # to exec python because it doesn't exist or isn't - # executable OR if exec isn't allowed (some restricted - # shells don't allow exec). If the exec succeeded, it will - # not return and not get to the "exit 97" command. If exec - # does return, we exit with code 97. - # - # Specifying the exact python program to run with --python - # avoids many of the issues above. However, if - # you have a restricted shell on remote, you may only be - # able to run python if it is in your PATH (and you can't - # run programs specified with an absolute path). In that - # case, sshuttle might not work at all since it is not - # possible to run python on the remote machine---even if - # it is present. - devnull = '/dev/null' - pycmd = ("P=python3; $P -V 2>%s || P=python; " - "exec \"$P\" -c %s; exit 97") % \ - (devnull, quote(pyscript)) - pycmd = ("/bin/sh -c {}".format(quote(pycmd))) + if remote_shell == "cmd": + pycmd = '"%s" -c "%s"' % (python or 'python', pyscript) + elif remote_shell == "powershell": + for c in ('\'', ' ', ';', '(', ')', ','): + pyscript = pyscript.replace(c, '`' + c) + pycmd = '%s -c %s' % (python or 'python', pyscript) + else: # posix shell expected + if python: + pycmd = '"%s" -c "%s"' % (python, pyscript) + else: + # By default, we run the following code in a shell. + # However, with restricted shells and other unusual + # situations, there can be trouble. See the RESTRICTED + # SHELL section in "man bash" for more information. The + # code makes many assumptions: + # + # (1) That /bin/sh exists and that we can call it. + # Restricted shells often do *not* allow you to run + # programs specified with an absolute path like /bin/sh. + # Either way, if there is trouble with this, it should + # return error code 127. + # + # (2) python3 or python exists in the PATH and is + # executable. If they aren't, then exec won't work (see (4) + # below). + # + # (3) In /bin/sh, that we can redirect stderr in order to + # hide the version that "python3 -V" might print (some + # restricted shells don't allow redirection, see + # RESTRICTED SHELL section in 'man bash'). However, if we + # are in a restricted shell, we'd likely have trouble with + # assumption (1) above. + # + # (4) The 'exec' command should work except if we failed + # to exec python because it doesn't exist or isn't + # executable OR if exec isn't allowed (some restricted + # shells don't allow exec). If the exec succeeded, it will + # not return and not get to the "exit 97" command. If exec + # does return, we exit with code 97. + # + # Specifying the exact python program to run with --python + # avoids many of the issues above. However, if + # you have a restricted shell on remote, you may only be + # able to run python if it is in your PATH (and you can't + # run programs specified with an absolute path). In that + # case, sshuttle might not work at all since it is not + # possible to run python on the remote machine---even if + # it is present. + devnull = '/dev/null' + pycmd = ("P=python3; $P -V 2>%s || P=python; " + "exec \"$P\" -c %s; exit 97") % \ + (devnull, quote(pyscript)) + pycmd = ("/bin/sh -c {}".format(quote(pycmd))) if password is not None: os.environ['SSHPASS'] = str(password)