Compare commits

...

44 Commits

Author SHA1 Message Date
abb48f1996 Update changes file 2020-08-24 08:00:36 +10:00
1c27a6cad0 Merge pull request #510 from sshuttle/dependabot/pip/attrs-20.1.0
Bump attrs from 19.3.0 to 20.1.0
2020-08-21 16:42:05 +10:00
8a2d5802c1 Bump attrs from 19.3.0 to 20.1.0
Bumps [attrs](https://github.com/python-attrs/attrs) from 19.3.0 to 20.1.0.
- [Release notes](https://github.com/python-attrs/attrs/releases)
- [Changelog](https://github.com/python-attrs/attrs/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/python-attrs/attrs/compare/19.3.0...20.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-21 06:37:11 +00:00
e7d4931b3d Merge pull request #507 from ddstreet/old_py
allow Mux() flush/fill to work with python < 3.5
2020-08-18 07:46:59 +10:00
1e364b2c0b Merge pull request #509 from sshuttle/dependabot/pip/pytest-cov-2.10.1
Bump pytest-cov from 2.10.0 to 2.10.1
2020-08-17 19:00:50 +10:00
8816dbfd23 Bump pytest-cov from 2.10.0 to 2.10.1
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.10.0 to 2.10.1.
- [Release notes](https://github.com/pytest-dev/pytest-cov/releases)
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.10.0...v2.10.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-17 08:18:23 +00:00
98d052d19e allow Mux() flush/fill to work with python < 3.5
Fixes: #503
2020-08-15 15:12:51 -04:00
be4b081a0d Merge pull request #506 from sshuttle/test_parse_hostport
Fix parse_hostport to always return string for host
2020-08-13 07:59:12 +10:00
9c5f1f5bbf Fix parse_hostport to always return string for host
This fixes #488 and provides an alternative solution for #489.
2020-08-13 07:53:38 +10:00
33d09ffcaf Merge pull request #501 from lnaundorf/patch-1
Add missing package in OpenWRT documentation
2020-08-12 07:56:10 +10:00
45f8cce2f8 Merge pull request #502 from joshuarli/ref/require-remote
fix: require -r/--remote
2020-08-12 07:36:37 +10:00
d4001c11f9 fix: workaround 2020-08-10 15:44:08 -07:00
450ad79b18 Revert "fix: require -r/--remote"
This reverts commit 5debf1f11a.
2020-08-10 15:31:20 -07:00
5debf1f11a fix: require -r/--remote 2020-08-10 15:12:24 -07:00
79181043bc Add missing package in OpenWRT documentation
The package 'iptables-mod-extra' also needs to be installed
2020-08-10 16:35:05 +02:00
c0a81353ab Fix doc about --listen option (#500)
* Can't use this option twice, separate by comma actually.

* Broke the line because it was too long.
2020-08-05 20:28:36 +10:00
5bdf36152a Merge pull request #498 from sshuttle/dependabot/pip/pytest-6.0.1
Bump pytest from 6.0.0 to 6.0.1
2020-08-01 18:07:00 +10:00
a9ee66d905 Bump pytest from 6.0.0 to 6.0.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.0.0...6.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-07-31 08:26:00 +00:00
094d3d9b97 Merge pull request #497 from sshuttle/dependabot/pip/pytest-6.0.0
Bump pytest from 5.4.3 to 6.0.0
2020-07-31 07:57:58 +10:00
19b677892e Bump pytest from 5.4.3 to 6.0.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 5.4.3 to 6.0.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/5.4.3...6.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-07-29 08:13:07 +00:00
319c122861 Merge pull request #495 from AsciiWolf/patch-1
README: add Ubuntu
2020-07-25 08:55:57 +10:00
f4bd290919 README: add Ubuntu 2020-07-24 17:38:16 +02:00
f353701f24 Merge pull request #490 from erikselin/42-is-not-the-answer
Douglas Adams and Deep Thought was wrong, 42 is not the answer
2020-07-17 11:09:04 +10:00
3037a91e51 Increase IP4 ttl to 63 hops instead of 42 2020-07-16 20:51:27 -04:00
cdd1e2c538 Merge pull request #487 from sshuttle/brianmay-patch-2
Fix formatting in installation.rst
2020-07-17 07:08:39 +10:00
eb01c0b184 Fix formatting in installation.rst 2020-07-15 08:14:51 +10:00
c5dcc918db Add changelog entry for 1.0.3 2020-07-12 19:09:32 +10:00
329b9cd0a0 Merge pull request #483 from chrisburr/patch-1
Fix formatting typos in usage docs
2020-07-05 11:00:18 +10:00
5537a90338 Fix formatting typos in usage docs 2020-07-04 12:02:44 +02:00
636e0442e5 Merge pull request #476 from sshuttle/brianmay-patch-1
Add missing import
2020-06-30 07:36:57 +10:00
dc526747b1 Add missing import
Fixes #474.
2020-06-27 18:54:02 +10:00
73eb3b6479 Merge pull request #471 from wilsonehusin/deprecate-py2-setuptools
Restrict setuptools from executing on Python2
2020-06-18 15:27:29 +10:00
1b50d364c6 Ask setuptools to require Python 3.5 and above
python_requires will be evaluated by setuptools to ensure the package is compatible
with currently active Python interpreter.

Reference: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires

Closes #470

Signed-off-by: Wilson Husin <wilsonehusin@gmail.com>
2020-06-17 22:17:03 -07:00
8c91958ff3 Prepare to release 1.0.2 2020-06-18 07:45:08 +10:00
d2f751f0d3 leave use of default port to ssh command
to prevent overwriting ports configured in ~/.ssh/config
if no port is specified, don't set the port explicitly to 22
2020-06-17 08:04:35 +10:00
9d79bb82c5 Bump pytest-cov from 2.9.0 to 2.10.0
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.9.0 to 2.10.0.
- [Release notes](https://github.com/pytest-dev/pytest-cov/releases)
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.9.0...v2.10.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-15 19:14:10 +10:00
a53f026056 Merge pull request #461 from joshuarli/doc/update-py2-removal
doc: py2 removal clean sweep
2020-06-11 09:55:36 +10:00
ae4c7e3a7b remove outdated comment 2020-06-10 16:47:23 -07:00
61bbbca956 another python 3.5 change 2020-06-10 16:46:52 -07:00
e56f8f2349 server side is py3.5+ 2020-06-10 16:46:10 -07:00
0a36eac686 ref: replace usage of deprecated imp (#449)
* Use types instead of imp.new_module.

I can follow up with https://docs.python.org/3/library/importlib.html#importlib.util.module_from_spec if need be.

* use source loader from importlib

* Revert "use source loader from importlib"

This reverts commit 1f255704f7.

* use inspect.getsource, but alas

* placate linter

* use find_spec to resolve a module spec to a file path

* better function naming

* remove outdated comment
2020-06-11 06:57:46 +10:00
16b462880b Merge pull request #460 from alekseymykhailov/fix_connection
fix connection with @ sign in username
2020-06-11 06:15:19 +10:00
500aa65693 fix connection with @ sign in username 2020-06-10 08:20:28 -07:00
7d998f6d42 Bump flake8 from 3.8.2 to 3.8.3
Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.8.2 to 3.8.3.
- [Release notes](https://gitlab.com/pycqa/flake8/tags)
- [Commits](https://gitlab.com/pycqa/flake8/compare/3.8.2...3.8.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-09 18:48:45 +10:00
18 changed files with 137 additions and 82 deletions

View File

@ -9,6 +9,42 @@ adheres to `Semantic Versioning`_.
.. _`Semantic Versioning`: http://semver.org/ .. _`Semantic Versioning`: http://semver.org/
1.0.3 - 2020-08-24
------------------
Fixed
~~~~~
* Allow Mux() flush/fill to work with python < 3.5
* Fix parse_hostport to always return string for host.
* Require -r/--remote parameter.
* Add missing package in OpenWRT documentation.
* Fix doc about --listen option.
* README: add Ubuntu.
* Increase IP4 ttl to 63 hops instead of 42.
* Fix formatting in installation.rst
1.0.3 - 2020-07-12
------------------
Fixed
~~~~~
* Ask setuptools to require Python 3.5 and above.
* Add missing import.
* Fix formatting typos in usage docs
1.0.2 - 2020-06-18
------------------
Fixed
~~~~~
* Leave use of default port to ssh command.
* Remove unwanted references to Python 2.7 in docs.
* Replace usage of deprecated imp.
* Fix connection with @ sign in username.
1.0.1 - 2020-06-05 1.0.1 - 2020-06-05
------------------ ------------------

View File

@ -30,6 +30,10 @@ common case:
Obtaining sshuttle Obtaining sshuttle
------------------ ------------------
- Ubuntu 16.04 or later::
apt-get install sshuttle
- Debian stretch or later:: - Debian stretch or later::
apt-get install sshuttle apt-get install sshuttle

View File

@ -6,7 +6,8 @@ Installation
pip install sshuttle pip install sshuttle
- Debain package manager:: - Debain package manager::
sudo apt install sshuttle
sudo apt install sshuttle
- Clone:: - Clone::
@ -18,5 +19,6 @@ Installation
Optionally after installation Optionally after installation
----------------------------- -----------------------------
- Add to sudoers file - Add to sudoers file::
sshuttle --sudoers
sshuttle --sudoers

View File

@ -11,7 +11,7 @@ Description
----------- -----------
:program:`sshuttle` allows you to create a VPN connection from your :program:`sshuttle` allows you to create a VPN connection from your
machine to any remote server that you can connect to via machine to any remote server that you can connect to via
ssh, as long as that server has python 2.3 or higher. ssh, as long as that server has python 3.5 or higher.
To work, you must have root access on the local machine, To work, you must have root access on the local machine,
but you can have a normal account on the server. but you can have a normal account on the server.
@ -65,7 +65,8 @@ Options
:program:`sshuttle`, e.g. ``--listen localhost``. :program:`sshuttle`, e.g. ``--listen localhost``.
For the tproxy and pf methods this can be an IPv6 address. Use this option For the tproxy and pf methods this can be an IPv6 address. Use this option
twice if required, to provide both IPv4 and IPv6 addresses. with comma separated values if required, to provide both IPv4 and IPv6
addresses, e.g. ``--listen 127.0.0.1:0,[::1]:0``.
.. option:: -H, --auto-hosts .. option:: -H, --auto-hosts

View File

@ -3,6 +3,6 @@ OpenWRT
Run:: Run::
opkg install python3 python3-pip iptables-mod-nat-extra iptables-mod-ipopt opkg install python3 python3-pip iptables-mod-extra iptables-mod-nat-extra iptables-mod-ipopt
python3 /usr/bin/pip3 install sshuttle python3 /usr/bin/pip3 install sshuttle
sshuttle -l 0.0.0.0 -r <IP> -x 192.168.1.1 0/0 sshuttle -l 0.0.0.0 -r <IP> -x 192.168.1.1 0/0

View File

@ -57,9 +57,8 @@ cmd.exe with Administrator access. See :doc:`windows` for more information.
Server side Requirements Server side Requirements
------------------------ ------------------------
The server can run in any version of Python between 2.4 and 3.6.
However it is recommended that you use Python 2.7, Python 3.5 or later whenever - Python 3.5 or greater.
possible as support for older versions might be dropped in the future.
Additional Suggested Software Additional Suggested Software

View File

@ -21,7 +21,7 @@ Forward all traffic::
sshuttle -r username@sshserver 0/0 sshuttle -r username@sshserver 0/0
- For 'My VPN broke and need a temporary solution FAST to access local IPv4 addresses': - For 'My VPN broke and need a temporary solution FAST to access local IPv4 addresses'::
sshuttle --dns -NHr username@sshserver 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 sshuttle --dns -NHr username@sshserver 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
@ -70,7 +70,7 @@ Sudoers File
sshuttle can auto-generate the proper sudoers.d file using the current user sshuttle can auto-generate the proper sudoers.d file using the current user
for Linux and OSX. Doing this will allow sshuttle to run without asking for for Linux and OSX. Doing this will allow sshuttle to run without asking for
the local sudo password and to give users who do not have sudo access the local sudo password and to give users who do not have sudo access
ability to run sshuttle. ability to run sshuttle::
sshuttle --sudoers sshuttle --sudoers
@ -82,14 +82,14 @@ option:`sshuttle --sudoers --sudoers-username {user_descriptor}` option. Valid
values for this vary based on how your system is configured. Values such as values for this vary based on how your system is configured. Values such as
usernames, groups pre-pended with `%` and sudoers user aliases will work. See usernames, groups pre-pended with `%` and sudoers user aliases will work. See
the sudoers manual for more information on valid user specif actions. the sudoers manual for more information on valid user specif actions.
The options must be used with `--sudoers` The options must be used with `--sudoers`::
sshuttle --sudoers --sudoers-user mike sshuttle --sudoers --sudoers-user mike
sshuttle --sudoers --sudoers-user %sudo sshuttle --sudoers --sudoers-user %sudo
The name of the file to be added to sudoers.d can be configured as well. This The name of the file to be added to sudoers.d can be configured as well. This
is mostly not necessary but can be useful for giving more than one user is mostly not necessary but can be useful for giving more than one user
access to sshuttle. The default is `sshuttle_auto` access to sshuttle. The default is `sshuttle_auto`::
sshuttle --sudoer --sudoers-filename sshuttle_auto_mike sshuttle --sudoer --sudoers-filename sshuttle_auto_mike
sshuttle --sudoer --sudoers-filename sshuttle_auto_tommy sshuttle --sudoer --sudoers-filename sshuttle_auto_tommy
@ -97,11 +97,11 @@ access to sshuttle. The default is `sshuttle_auto`
You can also see what configuration will be added to your system without You can also see what configuration will be added to your system without
modifying anything. This can be helpfull is the auto feature does not work, or modifying anything. This can be helpfull is the auto feature does not work, or
you want more control. This option also works with `--sudoers-username`. you want more control. This option also works with `--sudoers-username`.
`--sudoers-filename` has no effect with this option. `--sudoers-filename` has no effect with this option::
sshuttle --sudoers-no-modify sshuttle --sudoers-no-modify
This will simply sprint the generated configuration to STDOUT. Example This will simply sprint the generated configuration to STDOUT. Example::
08:40 PM william$ sshuttle --sudoers-no-modify 08:40 PM william$ sshuttle --sudoers-no-modify

View File

@ -1,7 +1,7 @@
-r requirements.txt -r requirements.txt
attrs==19.3.0 attrs==20.1.0
pytest==5.4.3 pytest==6.0.1
pytest-cov==2.9.0 pytest-cov==2.10.1
mock==2.0.0 mock==2.0.0
flake8==3.8.2 flake8==3.8.3
pyflakes==2.2.0 pyflakes==2.2.0

View File

@ -61,6 +61,7 @@ setup(
'sshuttle = sshuttle.cmdline:main', 'sshuttle = sshuttle.cmdline:main',
], ],
}, },
python_requires='>=3.5',
tests_require=[ tests_require=[
'pytest', 'pytest',
'pytest-cov', 'pytest-cov',

View File

@ -1,6 +1,6 @@
import sys import sys
import zlib import zlib
import imp import types
verbosity = verbosity # noqa: F821 must be a previously defined global verbosity = verbosity # noqa: F821 must be a previously defined global
z = zlib.decompressobj() z = zlib.decompressobj()
@ -15,7 +15,7 @@ while 1:
% (name, nbytes)) % (name, nbytes))
content = z.decompress(sys.stdin.read(nbytes)) content = z.decompress(sys.stdin.read(nbytes))
module = imp.new_module(name) module = types.ModuleType(name)
parents = name.rsplit(".", 1) parents = name.rsplit(".", 1)
if len(parents) == 2: if len(parents) == 2:
parent, parent_name = parents parent, parent_name = parents

View File

@ -557,6 +557,11 @@ def main(listenip_v6, listenip_v4,
subnets_include, subnets_exclude, daemon, to_nameserver, pidfile, subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
user, sudo_pythonpath): user, sudo_pythonpath):
if not remotename:
# XXX: We can't make it required at the argparse level,
# because sshuttle calls out to itself in FirewallClient.
raise Fatal("You must specify -r/--remote.")
if daemon: if daemon:
try: try:
check_daemon(pidfile) check_daemon(pidfile)

View File

@ -71,10 +71,10 @@ def ipt_ttl(family, *args):
global _no_ttl_module global _no_ttl_module
if not _no_ttl_module: if not _no_ttl_module:
# we avoid infinite loops by generating server-side connections # we avoid infinite loops by generating server-side connections
# with ttl 42. This makes the client side not recapture those # with ttl 63. This makes the client side not recapture those
# connections, in case client == server. # connections, in case client == server.
try: try:
argsplus = list(args) + ['-m', 'ttl', '!', '--ttl', '42'] argsplus = list(args) + ['-m', 'ttl', '!', '--ttl', '63']
ipt(family, *argsplus) ipt(family, *argsplus)
except Fatal: except Fatal:
ipt(family, *args) ipt(family, *args)

View File

@ -70,7 +70,7 @@ def ipfw_rule_exists(n):
found = False found = False
for line in p.stdout: for line in p.stdout:
if line.startswith(b'%05d ' % n): if line.startswith(b'%05d ' % n):
if not ('ipttl 42' in line or 'check-state' in line): if not ('ipttl 63' in line or 'check-state' in line):
log('non-sshuttle ipfw rule: %r\n' % line.strip()) log('non-sshuttle ipfw rule: %r\n' % line.strip())
raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n) raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n)
found = True found = True
@ -185,7 +185,7 @@ class Method(BaseMethod):
sender.setsockopt(socket.SOL_IP, IP_BINDANY, 1) sender.setsockopt(socket.SOL_IP, IP_BINDANY, 1)
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sender.setsockopt(socket.SOL_IP, socket.IP_TTL, 42) sender.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
sender.bind(srcip) sender.bind(srcip)
sender.sendto(data, dstip) sender.sendto(data, dstip)
sender.close() sender.close()
@ -224,7 +224,7 @@ class Method(BaseMethod):
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % port, ipfw('add', '1', 'fwd', '127.0.0.1,%d' % port,
'tcp', 'tcp',
'from', 'any', 'to', 'table(126)', 'from', 'any', 'to', 'table(126)',
'not', 'ipttl', '42', 'keep-state', 'setup') 'not', 'ipttl', '63', 'keep-state', 'setup')
ipfw_noexit('table', '124', 'flush') ipfw_noexit('table', '124', 'flush')
dnscount = 0 dnscount = 0
@ -235,11 +235,11 @@ class Method(BaseMethod):
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % dnsport, ipfw('add', '1', 'fwd', '127.0.0.1,%d' % dnsport,
'udp', 'udp',
'from', 'any', 'to', 'table(124)', 'from', 'any', 'to', 'table(124)',
'not', 'ipttl', '42') 'not', 'ipttl', '63')
ipfw('add', '1', 'allow', ipfw('add', '1', 'allow',
'udp', 'udp',
'from', 'any', 'to', 'any', 'from', 'any', 'to', 'any',
'ipttl', '42') 'ipttl', '63')
if subnets: if subnets:
# create new subnet entries # create new subnet entries

View File

@ -50,17 +50,17 @@ class Method(BaseMethod):
'ip daddr %s/%s' % (snet, swidth), 'return'))) 'ip daddr %s/%s' % (snet, swidth), 'return')))
else: else:
_nft('add rule', chain, *(tcp_ports + ( _nft('add rule', chain, *(tcp_ports + (
'ip daddr %s/%s' % (snet, swidth), 'ip ttl != 42', 'ip daddr %s/%s' % (snet, swidth), 'ip ttl != 63',
('redirect to :' + str(port))))) ('redirect to :' + str(port)))))
for _, ip in [i for i in nslist if i[0] == family]: for _, ip in [i for i in nslist if i[0] == family]:
if family == socket.AF_INET: if family == socket.AF_INET:
_nft('add rule', chain, 'ip protocol udp ip daddr %s' % ip, _nft('add rule', chain, 'ip protocol udp ip daddr %s' % ip,
'udp dport { 53 }', 'ip ttl != 42', 'udp dport { 53 }', 'ip ttl != 63',
('redirect to :' + str(dnsport))) ('redirect to :' + str(dnsport)))
elif family == socket.AF_INET6: elif family == socket.AF_INET6:
_nft('add rule', chain, 'ip6 protocol udp ip6 daddr %s' % ip, _nft('add rule', chain, 'ip6 protocol udp ip6 daddr %s' % ip,
'udp dport { 53 }', 'ip ttl != 42', 'udp dport { 53 }', 'ip ttl != 63',
('redirect to :' + str(dnsport))) ('redirect to :' + str(dnsport)))
def restore_firewall(self, port, family, udp, user): def restore_firewall(self, port, family, udp, user):

View File

@ -195,7 +195,7 @@ class DnsProxy(Handler):
family, sockaddr = self._addrinfo(peer, port) family, sockaddr = self._addrinfo(peer, port)
sock = socket.socket(family, socket.SOCK_DGRAM) sock = socket.socket(family, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42) sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
sock.connect(sockaddr) sock.connect(sockaddr)
self.peers[sock] = peer self.peers[sock] = peer
@ -252,7 +252,7 @@ class UdpProxy(Handler):
self.chan = chan self.chan = chan
self.sock = sock self.sock = sock
if family == socket.AF_INET: if family == socket.AF_INET:
self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42) self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
def send(self, dstip, data): def send(self, dstip, data):
debug2('UDP: sending to %r port %d\n' % dstip) debug2('UDP: sending to %r port %d\n' % dstip)

View File

@ -3,7 +3,8 @@ import os
import re import re
import socket import socket
import zlib import zlib
import imp import importlib
import importlib.util
import subprocess as ssubprocess import subprocess as ssubprocess
import shlex import shlex
from shlex import quote from shlex import quote
@ -14,43 +15,15 @@ import sshuttle.helpers as helpers
from sshuttle.helpers import debug2 from sshuttle.helpers import debug2
def readfile(name): def get_module_source(name):
tokens = name.split(".") spec = importlib.util.find_spec(name)
f = None with open(spec.origin, "rt") as f:
return f.read().encode("utf-8")
token = tokens[0]
token_name = [token]
token_str = ".".join(token_name)
try:
f, pathname, description = imp.find_module(token_str)
for token in tokens[1:]:
module = imp.load_module(token_str, f, pathname, description)
if f is not None:
f.close()
token_name.append(token)
token_str = ".".join(token_name)
f, pathname, description = imp.find_module(
token, module.__path__)
if f is not None:
contents = f.read()
else:
contents = ""
finally:
if f is not None:
f.close()
return contents.encode("UTF8")
def empackage(z, name, data=None): def empackage(z, name, data=None):
if not data: if not data:
data = readfile(name) data = get_module_source(name)
content = z.compress(data) content = z.compress(data)
content += z.flush(zlib.Z_SYNC_FLUSH) content += z.flush(zlib.Z_SYNC_FLUSH)
@ -68,17 +41,17 @@ def parse_hostport(rhostport):
and returns a tuple (username, password, port, host) and returns a tuple (username, password, port, host)
""" """
# default port for SSH is TCP port 22 # leave use of default port to ssh command to prevent overwriting
port = 22 # ports configured in ~/.ssh/config when no port is given
port = None
username = None username = None
password = None password = None
host = rhostport host = rhostport
if "@" in host: if "@" in host:
# split username (and possible password) from the host[:port] # split username (and possible password) from the host[:port]
username, host = host.split("@") username, host = host.rsplit("@", 1)
# Fix #410 bad username error detect # Fix #410 bad username error detect
# username cannot contain an @ sign in this scenario
if ":" in username: if ":" in username:
# this will even allow for the username to be empty # this will even allow for the username to be empty
username, password = username.split(":") username, password = username.split(":")
@ -92,12 +65,12 @@ def parse_hostport(rhostport):
try: try:
# try to parse host as an IP adress, # try to parse host as an IP adress,
# if that works it is an IPv6 address # if that works it is an IPv6 address
host = ipaddress.ip_address(host) host = str(ipaddress.ip_address(host))
except ValueError: except ValueError:
# if that fails parse as URL to get the port # if that fails parse as URL to get the port
parsed = urlparse('//{}'.format(host)) parsed = urlparse('//{}'.format(host))
try: try:
host = ipaddress.ip_address(parsed.hostname) host = str(ipaddress.ip_address(parsed.hostname))
except ValueError: except ValueError:
# else if both fails, we have a hostname with port # else if both fails, we have a hostname with port
host = parsed.hostname host = parsed.hostname
@ -117,7 +90,7 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
rhost = host rhost = host
z = zlib.compressobj(1) z = zlib.compressobj(1)
content = readfile('sshuttle.assembler') content = get_module_source('sshuttle.assembler')
optdata = ''.join("%s=%r\n" % (k, v) for (k, v) in list(options.items())) optdata = ''.join("%s=%r\n" % (k, v) for (k, v) in list(options.items()))
optdata = optdata.encode("UTF8") optdata = optdata.encode("UTF8")
content2 = (empackage(z, 'sshuttle') + content2 = (empackage(z, 'sshuttle') +
@ -145,6 +118,10 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
sshl = shlex.split(ssh_cmd) sshl = shlex.split(ssh_cmd)
else: else:
sshl = ['ssh'] sshl = ['ssh']
if port is not None:
portl = ["-p", str(port)]
else:
portl = []
if python: if python:
pycmd = "'%s' -c '%s'" % (python, pyscript) pycmd = "'%s' -c '%s'" % (python, pyscript)
else: else:
@ -155,12 +132,12 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
if password is not None: if password is not None:
os.environ['SSHPASS'] = str(password) os.environ['SSHPASS'] = str(password)
argv = (["sshpass", "-e"] + sshl + argv = (["sshpass", "-e"] + sshl +
["-p", str(port)] + portl +
[rhost, '--', pycmd]) [rhost, '--', pycmd])
else: else:
argv = (sshl + argv = (sshl +
["-p", str(port)] + portl +
[rhost, '--', pycmd]) [rhost, '--', pycmd])
(s1, s2) = socket.socketpair() (s1, s2) = socket.socketpair()

View File

@ -4,21 +4,19 @@ import socket
import errno import errno
import select import select
import os import os
import fcntl
from sshuttle.helpers import b, log, debug1, debug2, debug3, Fatal from sshuttle.helpers import b, log, debug1, debug2, debug3, Fatal
MAX_CHANNEL = 65535 MAX_CHANNEL = 65535
LATENCY_BUFFER_SIZE = 32768 LATENCY_BUFFER_SIZE = 32768
# these don't exist in the socket module in python 2.3!
SHUT_RD = 0 SHUT_RD = 0
SHUT_WR = 1 SHUT_WR = 1
SHUT_RDWR = 2 SHUT_RDWR = 2
HDR_LEN = 8 HDR_LEN = 8
CMD_EXIT = 0x4200 CMD_EXIT = 0x4200
CMD_PING = 0x4201 CMD_PING = 0x4201
CMD_PONG = 0x4202 CMD_PONG = 0x4202
@ -439,7 +437,13 @@ class Mux(Handler):
callback(cmd, data) callback(cmd, data)
def flush(self): def flush(self):
os.set_blocking(self.wfile.fileno(), False) try:
os.set_blocking(self.wfile.fileno(), False)
except AttributeError:
# python < 3.5
flags = fcntl.fcntl(self.wfile.fileno(), fcntl.F_GETFL)
flags |= os.O_NONBLOCK
flags = fcntl.fcntl(self.wfile.fileno(), fcntl.F_SETFL, flags)
if self.outbuf and self.outbuf[0]: if self.outbuf and self.outbuf[0]:
wrote = _nb_clean(os.write, self.wfile.fileno(), self.outbuf[0]) wrote = _nb_clean(os.write, self.wfile.fileno(), self.outbuf[0])
debug2('mux wrote: %r/%d\n' % (wrote, len(self.outbuf[0]))) debug2('mux wrote: %r/%d\n' % (wrote, len(self.outbuf[0])))
@ -449,7 +453,13 @@ class Mux(Handler):
self.outbuf[0:1] = [] self.outbuf[0:1] = []
def fill(self): def fill(self):
os.set_blocking(self.rfile.fileno(), False) try:
os.set_blocking(self.rfile.fileno(), False)
except AttributeError:
# python < 3.5
flags = fcntl.fcntl(self.rfile.fileno(), fcntl.F_GETFL)
flags |= os.O_NONBLOCK
flags = fcntl.fcntl(self.rfile.fileno(), fcntl.F_SETFL, flags)
try: try:
read = _nb_clean(os.read, self.rfile.fileno(), LATENCY_BUFFER_SIZE) read = _nb_clean(os.read, self.rfile.fileno(), LATENCY_BUFFER_SIZE)
except OSError: except OSError:
@ -573,7 +583,7 @@ class MuxWrapper(SockWrapper):
def connect_dst(family, ip, port): def connect_dst(family, ip, port):
debug2('Connecting to %s:%d\n' % (ip, port)) debug2('Connecting to %s:%d\n' % (ip, port))
outsock = socket.socket(family) outsock = socket.socket(family)
outsock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42) outsock.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
return SockWrapper(outsock, outsock, return SockWrapper(outsock, outsock,
connect_to=(ip, port), connect_to=(ip, port),
peername='%s:%d' % (ip, port)) peername='%s:%d' % (ip, port))

View File

@ -0,0 +1,20 @@
from sshuttle.ssh import parse_hostport
def test_host_only():
assert parse_hostport("host") == (None, None, None, "host")
assert parse_hostport("1.2.3.4") == (None, None, None, "1.2.3.4")
assert parse_hostport("2001::1") == (None, None, None, "2001::1")
assert parse_hostport("[2001::1]") == (None, None, None, "2001::1")
def test_host_and_port():
assert parse_hostport("host:22") == (None, None, 22, "host")
assert parse_hostport("1.2.3.4:22") == (None, None, 22, "1.2.3.4")
assert parse_hostport("[2001::1]:22") == (None, None, 22, "2001::1")
def test_username_and_host():
assert parse_hostport("user@host") == ("user", None, None, "host")
assert parse_hostport("user:@host") == ("user", None, None, "host")
assert parse_hostport("user:pass@host") == ("user", "pass", None, "host")