From 966fd0c5231d56c4f464752865d96f97bd3c0aac Mon Sep 17 00:00:00 2001 From: tobigrimm Date: Sat, 25 Apr 2020 01:40:39 +0200 Subject: [PATCH] Fix parsing of hostnames to allow ssh aliases defined in ssh configs) (#418) * Fix parsing of hostnames to allow ssh aliases defined in ssh configs) * nicer formatting, pep8 applied * Properly parse IPv6 addresses with port specification * Now also handles hostnames with port specified and IPv6 addresses without port properly * Updated parameter description for the remotehost specification * Make the urlparse import backwards compatible to python2 Co-authored-by: Tobi --- sshuttle/options.py | 4 +-- sshuttle/ssh.py | 79 ++++++++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 32 deletions(-) diff --git a/sshuttle/options.py b/sshuttle/options.py index 79c404b..12ce55d 100644 --- a/sshuttle/options.py +++ b/sshuttle/options.py @@ -177,9 +177,9 @@ parser.add_argument( ) parser.add_argument( "-r", "--remote", - metavar="[USERNAME@]ADDR[:PORT]", + metavar="[USERNAME[:PASSWORD]@]ADDR[:PORT]", help=""" - ssh hostname (and optional username) of remote %(prog)s server + ssh hostname (and optional username and password) of remote %(prog)s server """ ) parser.add_argument( diff --git a/sshuttle/ssh.py b/sshuttle/ssh.py index 9f99ff7..72951f6 100644 --- a/sshuttle/ssh.py +++ b/sshuttle/ssh.py @@ -6,6 +6,14 @@ import zlib import imp import subprocess as ssubprocess import shlex +import ipaddress + +# ensure backwards compatiblity with python2.7 +try: + from urllib.parse import urlparse +except ImportError: + from urlparse import urlparse + import sshuttle.helpers as helpers from sshuttle.helpers import debug2 @@ -61,53 +69,64 @@ def empackage(z, name, data=None): def parse_hostport(rhostport): - # default define variable - port = "" - username = re.split(r'\s*:', rhostport)[0] + """ + parses the given rhostport variable, looking like this: - # Fix #410 bad username error detect - if "@" in username: - username = re.split(r'\s*@', rhostport)[0] + [username[:password]@]host[:port] + if only host is given, can be a hostname, IPv4/v6 address or a ssh alias + from ~/.ssh/config + + and returns a tuple (username, password, port, host) + """ + # default port for SSH is TCP port 22 + port = 22 + username = None password = None - host = None + host = rhostport - try: - password = re.split(r'\s*:', rhostport)[1] - if "@" in password: - password = password.split("@")[0] - except (IndexError, TypeError): - pass + if "@" in host: + # split username (and possible password) from the host[:port] + username, host = host.split("@") + # Fix #410 bad username error detect + # username cannot contain an @ sign in this scenario + if ":" in username: + # this will even allow for the username to be empty + username, password = username.split(":") - if password is None or "@" in password: - # default define password - password = None - host = password + if ":" in host: + # IPv6 address and/or got a port specified - if host is None: - # split for ipv4 or ipv6 - host = "{}".format(re.split(r'\s*@', rhostport)[1]) + # If it is an IPv6 adress with port specification, + # then it will look like: [::1]:22 - # try if port define try: - # Fix #410 detect host:port - port = re.split(r'\s*:', host)[1] - host = re.split(r'\s*:', host)[0] - except IndexError: - pass + # try to parse host as an IP adress, + # if that works it is an IPv6 address + host = ipaddress.ip_address(host) + except ValueError: + # if that fails parse as URL to get the port + parsed = urlparse('//{}'.format(host)) + try: + host = ipaddress.ip_address(parsed.hostname) + except ValueError: + # else if both fails, we have a hostname with port + host = parsed.hostname + port = parsed.port - if port == "": - port = 22 if password is None or len(password) == 0: password = None return username, password, port, host + def connect(ssh_cmd, rhostport, python, stderr, options): username, password, port, host = parse_hostport(rhostport) - - rhost = "{}@{}".format(username, host) + if username: + rhost = "{}@{}".format(username, host) + else: + rhost = host z = zlib.compressobj(1) content = readfile('sshuttle.assembler')