mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-07-08 18:47:25 +02:00
Compare commits
66 Commits
Author | SHA1 | Date | |
---|---|---|---|
9df7a0a053 | |||
a28c8ae10b | |||
4f4d6d9f4d | |||
a1c7e64b0e | |||
88139ed2e5 | |||
810b4a3170 | |||
98233530a0 | |||
77eb8167c4 | |||
a6efc6b653 | |||
f8086dfa59 | |||
58d72a93d2 | |||
6929b79274 | |||
bf4fa6cacc | |||
2462d6d204 | |||
86c69dda48 | |||
df98790206 | |||
f9a9dad9ff | |||
1fa47bf8e1 | |||
7525f8d4c5 | |||
a33a4829e2 | |||
90ec0a9cb6 | |||
0914bef9a2 | |||
93200f7095 | |||
1def53e085 | |||
553bc2b70c | |||
bf4cb64f25 | |||
004365f5c7 | |||
d6fa0c1462 | |||
9e3209e931 | |||
7d67231faf | |||
0b267cdeff | |||
30cdc5e74b | |||
181bf648a7 | |||
10341f3ad6 | |||
6f92bd8ccf | |||
a7ca6d47a6 | |||
6d36916f48 | |||
5719d424de | |||
9431bb7a2f | |||
8c94b55d30 | |||
1ed09fbe72 | |||
ce7b4f83b2 | |||
d9d3533b82 | |||
0932bdd231 | |||
f4150b7283 | |||
bfd6f5d088 | |||
016919cf95 | |||
48ab82b81e | |||
d8a07a5244 | |||
2f5c946b48 | |||
1d4c059f44 | |||
b9b89c3f55 | |||
e5eb5afef0 | |||
19e2a1810d | |||
2f026c84af | |||
04214eaf89 | |||
6b07cb2d21 | |||
b1aa5fef89 | |||
d378cbd582 | |||
166e4d6742 | |||
317211a974 | |||
c28976a10e | |||
09c534bcf3 | |||
0c3b615736 | |||
c783fdb472 | |||
0f92735ee5 |
70
.github/workflows/codeql.yml
vendored
Normal file
70
.github/workflows/codeql.yml
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '31 21 * * 3'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
6
.github/workflows/pythonpackage.yml
vendored
6
.github/workflows/pythonpackage.yml
vendored
@ -17,12 +17,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
|
||||
python-version: ["3.8", "3.9", "3.10"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2.3.1
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,5 @@
|
||||
/sshuttle/version.py
|
||||
/tmp/
|
||||
/.coverage
|
||||
/.cache/
|
||||
/.eggs/
|
||||
/.tox/
|
||||
|
@ -6,10 +6,10 @@ build:
|
||||
python: "3.9"
|
||||
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
configuration: docs/conf.py
|
||||
|
||||
python:
|
||||
install:
|
||||
- requirements: requirements.txt
|
||||
- method: setuptools
|
||||
path: .
|
||||
install:
|
||||
- requirements: requirements.txt
|
||||
- method: setuptools
|
||||
path: .
|
||||
|
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@ -0,0 +1 @@
|
||||
python 3.10.6
|
@ -1,84 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# William Mantly <wmantly@gmail.com>
|
||||
# MIT License
|
||||
# https://github.com/wmantly/sudoers-add
|
||||
|
||||
NEWLINE=$'\n'
|
||||
CONTENT=""
|
||||
ME="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")"
|
||||
|
||||
if [ "$1" == "--help" ] || [ "$1" == "-h" ]; then
|
||||
echo "Usage: $ME [file_path] [sudoers-file-name]"
|
||||
echo "Usage: [content] | $ME sudoers-file-name"
|
||||
echo "This will take a sudoers config validate it and add it to /etc/sudoers.d/{sudoers-file-name}"
|
||||
echo "The config can come from a file, first usage example or piped in second example."
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$1" == "" ]; then
|
||||
(>&2 echo "This command take at lest one argument. See $ME --help")
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$2" == "" ]; then
|
||||
FILE_NAME=$1
|
||||
shift
|
||||
else
|
||||
FILE_NAME=$2
|
||||
fi
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "This script must be run as root"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while read -r line
|
||||
do
|
||||
CONTENT+="${line}${NEWLINE}"
|
||||
done < "${1:-/dev/stdin}"
|
||||
|
||||
if [ "$CONTENT" == "" ]; then
|
||||
(>&2 echo "No config content specified. See $ME --help")
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$FILE_NAME" == "" ]; then
|
||||
(>&2 echo "No sudoers file name specified. See $ME --help")
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify that the resulting file name begins with /etc/sudoers.d
|
||||
FILE_NAME="$(realpath "/etc/sudoers.d/$FILE_NAME")"
|
||||
if [[ "$FILE_NAME" != "/etc/sudoers.d/"* ]] ; then
|
||||
echo -n "Invalid sudoers filename: Final sudoers file "
|
||||
echo "location ($FILE_NAME) does not begin with /etc/sudoers.d"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Make a temp file to hold the sudoers config
|
||||
umask 077
|
||||
TEMP_FILE=$(mktemp)
|
||||
echo "$CONTENT" > "$TEMP_FILE"
|
||||
|
||||
# Make sure the content is valid
|
||||
visudo_STDOUT=$(visudo -c -f "$TEMP_FILE" 2>&1)
|
||||
visudo_code=$?
|
||||
# The temp file is no longer needed
|
||||
rm "$TEMP_FILE"
|
||||
|
||||
if [ $visudo_code -eq 0 ]; then
|
||||
echo "$CONTENT" > "$FILE_NAME"
|
||||
chmod 0440 "$FILE_NAME"
|
||||
echo "The sudoers file $FILE_NAME has been successfully created!"
|
||||
|
||||
exit 0
|
||||
else
|
||||
echo "Invalid sudoers config!"
|
||||
echo "$visudo_STDOUT"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
@ -19,6 +19,5 @@ Installation
|
||||
Optionally after installation
|
||||
-----------------------------
|
||||
|
||||
- Add to sudoers file::
|
||||
- Install sudoers configuration. For details, see the "Sudoers File" section in :doc:`usage`
|
||||
|
||||
sshuttle --sudoers
|
||||
|
@ -242,8 +242,8 @@ Options
|
||||
|
||||
.. option:: --disable-ipv6
|
||||
|
||||
Disable IPv6 support for methods that support it (nft, tproxy, and
|
||||
pf).
|
||||
Disable IPv6 support for methods that support it (nat, nft,
|
||||
tproxy, and pf).
|
||||
|
||||
.. option:: --firewall
|
||||
|
||||
@ -262,28 +262,23 @@ Options
|
||||
makes it a lot easier to debug and test the :option:`--auto-hosts`
|
||||
feature.
|
||||
|
||||
.. option:: --sudoers
|
||||
|
||||
sshuttle will auto generate the proper sudoers.d config file and add it.
|
||||
Once this is completed, sshuttle will exit and tell the user if
|
||||
it succeed or not. Do not call this options with sudo, it may generate a
|
||||
incorrect config file.
|
||||
|
||||
.. option:: --sudoers-no-modify
|
||||
|
||||
sshuttle will auto generate the proper sudoers.d config and print it to
|
||||
stdout. The option will not modify the system at all.
|
||||
sshuttle prints a configuration to stdout which allows a user to
|
||||
run sshuttle without a password. This option is INSECURE because,
|
||||
with some cleverness, it also allows the user to run any command
|
||||
as root without a password. The output also includes a suggested
|
||||
method for you to install the configuration.
|
||||
|
||||
Use --sudoers-user to modify the user that it applies to.
|
||||
|
||||
.. option:: --sudoers-user
|
||||
|
||||
Set the user name or group with %group_name for passwordless operation.
|
||||
Default is the current user.set ALL for all users. Only works with
|
||||
--sudoers or --sudoers-no-modify option.
|
||||
|
||||
.. option:: --sudoers-filename
|
||||
|
||||
Set the file name for the sudoers.d file to be added. Default is
|
||||
"sshuttle_auto". Only works with --sudoers.
|
||||
Set the user name or group with %group_name for passwordless
|
||||
operation. Default is the current user. Set to ALL for all users
|
||||
(NOT RECOMMENDED: See note about security in --sudoers-no-modify
|
||||
documentation above). Only works with the --sudoers-no-modify
|
||||
option.
|
||||
|
||||
.. option:: -t <mark>, --tmark=<mark>
|
||||
|
||||
@ -460,7 +455,7 @@ Packet-level forwarding (eg. using the tun/tap devices on
|
||||
Linux) seems elegant at first, but it results in
|
||||
several problems, notably the 'tcp over tcp' problem. The
|
||||
tcp protocol depends fundamentally on packets being dropped
|
||||
in order to implement its congestion control agorithm; if
|
||||
in order to implement its congestion control algorithm; if
|
||||
you pass tcp packets through a tcp-based tunnel (such as
|
||||
ssh), the inner tcp packets will never be dropped, and so
|
||||
the inner tcp stream's congestion control will be
|
||||
|
@ -6,7 +6,7 @@ Client side Requirements
|
||||
|
||||
- sudo, or root access on your client machine.
|
||||
(The server doesn't need admin access.)
|
||||
- Python 3.6 or greater.
|
||||
- Python 3.8 or greater.
|
||||
|
||||
|
||||
Linux with NAT method
|
||||
@ -72,7 +72,7 @@ cmd.exe with Administrator access. See :doc:`windows` for more information.
|
||||
Server side Requirements
|
||||
------------------------
|
||||
|
||||
- Python 3.6 or greater.
|
||||
- Python 3.8 or greater.
|
||||
|
||||
|
||||
Additional Suggested Software
|
||||
|
@ -71,44 +71,23 @@ admin access on the server.
|
||||
|
||||
Sudoers File
|
||||
------------
|
||||
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
|
||||
the local sudo password and to give users who do not have sudo access
|
||||
ability to run sshuttle::
|
||||
|
||||
sshuttle --sudoers
|
||||
sshuttle can generate a sudoers.d file for Linux and MacOS. This
|
||||
allows one or more users to run sshuttle without entering the
|
||||
local sudo password. **WARNING:** This option is *insecure*
|
||||
because, with some cleverness, it also allows these users to run any
|
||||
command (via the --ssh-cmd option) as root without a password.
|
||||
|
||||
DO NOT run this command with sudo, it will ask for your sudo password when
|
||||
it is needed.
|
||||
|
||||
A costume user or group can be set with the :
|
||||
option:`sshuttle --sudoers --sudoers-username {user_descriptor}` option. Valid
|
||||
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
|
||||
the sudoers manual for more information on valid user specif actions.
|
||||
The options must be used with `--sudoers`::
|
||||
|
||||
sshuttle --sudoers --sudoers-user mike
|
||||
sshuttle --sudoers --sudoers-user %sudo
|
||||
|
||||
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
|
||||
access to sshuttle. The default is `sshuttle_auto`::
|
||||
|
||||
sshuttle --sudoer --sudoers-filename sshuttle_auto_mike
|
||||
sshuttle --sudoer --sudoers-filename sshuttle_auto_tommy
|
||||
|
||||
You can also see what configuration will be added to your system without
|
||||
modifying anything. This can be helpful if the auto feature does not work, or
|
||||
you want more control. This option also works with `--sudoers-username`.
|
||||
`--sudoers-filename` has no effect with this option::
|
||||
To print a sudo configuration file and see a suggested way to install it, run::
|
||||
|
||||
sshuttle --sudoers-no-modify
|
||||
|
||||
This will simply sprint the generated configuration to STDOUT. Example::
|
||||
A custom user or group can be set with the
|
||||
:option:`sshuttle --sudoers-no-modify --sudoers-user {user_descriptor}`
|
||||
option. Valid 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 the sudoers manual for more information on valid
|
||||
user specif actions. The option must be used with `--sudoers-no-modify`::
|
||||
|
||||
08:40 PM william$ sshuttle --sudoers-no-modify
|
||||
|
||||
Cmnd_Alias SSHUTTLE304 = /usr/bin/env PYTHONPATH=/usr/local/lib/python2.7/dist-packages/sshuttle-0.78.5.dev30+gba5e6b5.d20180909-py2.7.egg /usr/bin/python /usr/local/bin/sshuttle --method auto --firewall
|
||||
|
||||
william ALL=NOPASSWD: SSHUTTLE304
|
||||
sshuttle --sudoers-no-modify --sudoers-user mike
|
||||
sshuttle --sudoers-no-modify --sudoers-user %sudo
|
||||
|
@ -1,5 +1,6 @@
|
||||
-r requirements.txt
|
||||
pytest==6.2.5
|
||||
pytest==7.1.3
|
||||
pytest-cov==3.0.0
|
||||
flake8==4.0.1
|
||||
pyflakes==2.4.0
|
||||
flake8==5.0.4
|
||||
pyflakes==2.5.0
|
||||
bump2version==1.0.1
|
||||
|
@ -1,2 +1 @@
|
||||
setuptools-scm==6.4.2
|
||||
Sphinx==4.3.2
|
||||
Sphinx==5.1.1
|
||||
|
20
setup.cfg
20
setup.cfg
@ -1,17 +1,25 @@
|
||||
[bumpversion]
|
||||
current_version = 1.1.1
|
||||
|
||||
[bumpversion:file:setup.py]
|
||||
|
||||
[bumpversion:file:sshuttle/version.py]
|
||||
|
||||
[aliases]
|
||||
test=pytest
|
||||
test = pytest
|
||||
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[upload]
|
||||
sign=true
|
||||
identity=0x1784577F811F6EAC
|
||||
sign = true
|
||||
identity = 0x1784577F811F6EAC
|
||||
|
||||
[flake8]
|
||||
count=true
|
||||
show-source=true
|
||||
statistics=true
|
||||
count = true
|
||||
show-source = true
|
||||
statistics = true
|
||||
max-line-length = 128
|
||||
|
||||
[tool:pytest]
|
||||
addopts = --cov=sshuttle --cov-branch --cov-report=term-missing
|
||||
|
21
setup.py
21
setup.py
@ -20,20 +20,9 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
def version_scheme(version):
|
||||
from setuptools_scm.version import guess_next_dev_version
|
||||
version = guess_next_dev_version(version)
|
||||
return version.lstrip("v")
|
||||
|
||||
|
||||
setup(
|
||||
name="sshuttle",
|
||||
use_scm_version={
|
||||
'write_to': "sshuttle/version.py",
|
||||
'version_scheme': version_scheme,
|
||||
},
|
||||
setup_requires=['setuptools_scm'],
|
||||
# version=version,
|
||||
version='1.1.1',
|
||||
url='https://github.com/sshuttle/sshuttle',
|
||||
author='Brian May',
|
||||
author_email='brian@linuxpenguins.xyz',
|
||||
@ -46,22 +35,20 @@ setup(
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
"License :: OSI Approved :: "
|
||||
"License :: OSI Approved :: " +
|
||||
"GNU Lesser General Public License v2 or later (LGPLv2+)",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Topic :: System :: Networking",
|
||||
],
|
||||
scripts=['bin/sudoers-add'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'sshuttle = sshuttle.cmdline:main',
|
||||
],
|
||||
},
|
||||
python_requires='>=3.6',
|
||||
python_requires='>=3.8',
|
||||
install_requires=[
|
||||
],
|
||||
tests_require=[
|
||||
|
@ -123,14 +123,14 @@ class MultiListener:
|
||||
self.bind_called = False
|
||||
|
||||
def setsockopt(self, level, optname, value):
|
||||
assert(self.bind_called)
|
||||
assert self.bind_called
|
||||
if self.v6:
|
||||
self.v6.setsockopt(level, optname, value)
|
||||
if self.v4:
|
||||
self.v4.setsockopt(level, optname, value)
|
||||
|
||||
def add_handler(self, handlers, callback, method, mux):
|
||||
assert(self.bind_called)
|
||||
assert self.bind_called
|
||||
socks = []
|
||||
if self.v6:
|
||||
socks.append(self.v6)
|
||||
@ -145,7 +145,7 @@ class MultiListener:
|
||||
)
|
||||
|
||||
def listen(self, backlog):
|
||||
assert(self.bind_called)
|
||||
assert self.bind_called
|
||||
if self.v6:
|
||||
self.v6.listen(backlog)
|
||||
if self.v4:
|
||||
@ -160,11 +160,26 @@ class MultiListener:
|
||||
raise e
|
||||
|
||||
def bind(self, address_v6, address_v4):
|
||||
assert(not self.bind_called)
|
||||
assert not self.bind_called
|
||||
self.bind_called = True
|
||||
if address_v6 is not None:
|
||||
self.v6 = socket.socket(socket.AF_INET6, self.type, self.proto)
|
||||
self.v6.bind(address_v6)
|
||||
try:
|
||||
self.v6.bind(address_v6)
|
||||
except OSError as e:
|
||||
if e.errno == errno.EADDRNOTAVAIL:
|
||||
# On an IPv6 Linux machine, this situation occurs
|
||||
# if you run the following prior to running
|
||||
# sshuttle:
|
||||
#
|
||||
# echo 1 > /proc/sys/net/ipv6/conf/all/disable_ipv6
|
||||
# echo 1 > /proc/sys/net/ipv6/conf/default/disable_ipv6
|
||||
raise Fatal("Could not bind to an IPv6 socket with "
|
||||
"address %s and port %s. "
|
||||
"Potential workaround: Run sshuttle "
|
||||
"with '--disable-ipv6'."
|
||||
% (str(address_v6[0]), str(address_v6[1])))
|
||||
raise e
|
||||
else:
|
||||
self.v6 = None
|
||||
if address_v4 is not None:
|
||||
@ -174,7 +189,7 @@ class MultiListener:
|
||||
self.v4 = None
|
||||
|
||||
def print_listening(self, what):
|
||||
assert(self.bind_called)
|
||||
assert self.bind_called
|
||||
if self.v6:
|
||||
listenip = self.v6.getsockname()
|
||||
debug1('%s listening on %r.' % (what, listenip))
|
||||
@ -205,8 +220,8 @@ class FirewallClient:
|
||||
else:
|
||||
# Linux typically uses sudo; OpenBSD uses doas. However, some
|
||||
# Linux distributions are starting to use doas.
|
||||
sudo_cmd = ['sudo', '-p', '[local sudo] Password: ']+argvbase
|
||||
doas_cmd = ['doas']+argvbase
|
||||
sudo_cmd = ['sudo', '-p', '[local sudo] Password: ']
|
||||
doas_cmd = ['doas']
|
||||
|
||||
# For clarity, try to replace executable name with the
|
||||
# full path.
|
||||
@ -225,8 +240,13 @@ class FirewallClient:
|
||||
pp_prefix = ['/usr/bin/env',
|
||||
'PYTHONPATH=%s' %
|
||||
os.path.dirname(os.path.dirname(__file__))]
|
||||
sudo_cmd = pp_prefix + sudo_cmd
|
||||
doas_cmd = pp_prefix + doas_cmd
|
||||
sudo_cmd = sudo_cmd + pp_prefix
|
||||
doas_cmd = doas_cmd + pp_prefix
|
||||
|
||||
# Final order should be: sudo/doas command, env
|
||||
# pythonpath, and then argvbase (sshuttle command).
|
||||
sudo_cmd = sudo_cmd + argvbase
|
||||
doas_cmd = doas_cmd + argvbase
|
||||
|
||||
# If we can find doas and not sudo or if we are on
|
||||
# OpenBSD, try using doas first.
|
||||
@ -354,8 +374,8 @@ class FirewallClient:
|
||||
raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
|
||||
|
||||
def sethostip(self, hostname, ip):
|
||||
assert(not re.search(br'[^-\w\.]', hostname))
|
||||
assert(not re.search(br'[^0-9.]', ip))
|
||||
assert not re.search(br'[^-\w\.]', hostname)
|
||||
assert not re.search(br'[^0-9.]', ip)
|
||||
self.pfile.write(b'HOST %s,%s\n' % (hostname, ip))
|
||||
self.pfile.flush()
|
||||
|
||||
@ -953,7 +973,7 @@ def main(listenip_v6, listenip_v4,
|
||||
raise e
|
||||
|
||||
if not bound:
|
||||
assert(last_e)
|
||||
assert last_e
|
||||
raise last_e
|
||||
tcp_listener.listen(10)
|
||||
tcp_listener.print_listening("TCP redirector")
|
||||
@ -999,7 +1019,7 @@ def main(listenip_v6, listenip_v4,
|
||||
|
||||
dns_listener.print_listening("DNS")
|
||||
if not bound:
|
||||
assert(last_e)
|
||||
assert last_e
|
||||
raise last_e
|
||||
else:
|
||||
dnsport_v6 = 0
|
||||
|
@ -1,6 +1,5 @@
|
||||
import re
|
||||
import socket
|
||||
import platform
|
||||
import sshuttle.helpers as helpers
|
||||
import sshuttle.client as client
|
||||
import sshuttle.firewall as firewall
|
||||
@ -14,20 +13,9 @@ from sshuttle.sudoers import sudoers
|
||||
def main():
|
||||
opt = parser.parse_args()
|
||||
|
||||
if opt.sudoers or opt.sudoers_no_modify:
|
||||
if platform.platform().startswith('OpenBSD'):
|
||||
log('Automatic sudoers does not work on BSD')
|
||||
return 1
|
||||
|
||||
if not opt.sudoers_filename:
|
||||
log('--sudoers-file must be set or omitted.')
|
||||
return 1
|
||||
|
||||
sudoers(
|
||||
user_name=opt.sudoers_user,
|
||||
no_modify=opt.sudoers_no_modify,
|
||||
file_name=opt.sudoers_filename
|
||||
)
|
||||
if opt.sudoers_no_modify:
|
||||
# sudoers() calls exit() when it completes
|
||||
sudoers(user_name=opt.sudoers_user)
|
||||
|
||||
if opt.daemon:
|
||||
opt.syslog = 1
|
||||
@ -45,7 +33,8 @@ def main():
|
||||
parser.error('exactly zero arguments expected')
|
||||
return firewall.main(opt.method, opt.syslog)
|
||||
elif opt.hostwatch:
|
||||
return hostwatch.hw_main(opt.subnets, opt.auto_hosts)
|
||||
hostwatch.hw_main(opt.subnets, opt.auto_hosts)
|
||||
return 0
|
||||
else:
|
||||
# parse_subnetports() is used to create a list of includes
|
||||
# and excludes. It is called once for each parameter and
|
||||
|
@ -1,4 +1,5 @@
|
||||
import errno
|
||||
import shutil
|
||||
import socket
|
||||
import signal
|
||||
import sys
|
||||
@ -30,7 +31,11 @@ def rewrite_etc_hosts(hostmap, port):
|
||||
else:
|
||||
raise
|
||||
if old_content.strip() and not os.path.exists(BAKFILE):
|
||||
os.link(HOSTSFILE, BAKFILE)
|
||||
try:
|
||||
os.link(HOSTSFILE, BAKFILE)
|
||||
except OSError:
|
||||
# file is locked - performing non-atomic copy
|
||||
shutil.copyfile(HOSTSFILE, BAKFILE)
|
||||
tmpname = "%s.%d.tmp" % (HOSTSFILE, port)
|
||||
f = open(tmpname, 'w')
|
||||
for line in old_content.rstrip().split('\n'):
|
||||
@ -46,8 +51,14 @@ def rewrite_etc_hosts(hostmap, port):
|
||||
os.chmod(tmpname, st.st_mode)
|
||||
else:
|
||||
os.chown(tmpname, 0, 0)
|
||||
os.chmod(tmpname, 0o600)
|
||||
os.rename(tmpname, HOSTSFILE)
|
||||
os.chmod(tmpname, 0o644)
|
||||
try:
|
||||
os.rename(tmpname, HOSTSFILE)
|
||||
except OSError:
|
||||
# file is locked - performing non-atomic copy
|
||||
log('Warning: Using a non-atomic way to overwrite %s that can corrupt the file if '
|
||||
'multiple processes write to it simultaneously.' % HOSTSFILE)
|
||||
shutil.move(tmpname, HOSTSFILE)
|
||||
|
||||
|
||||
def restore_etc_hosts(hostmap, port):
|
||||
@ -199,8 +210,8 @@ def main(method_name, syslog):
|
||||
break
|
||||
try:
|
||||
(family, width, exclude, ip, fport, lport) = \
|
||||
line.strip().split(',', 5)
|
||||
except BaseException:
|
||||
line.strip().split(',', 5)
|
||||
except Exception:
|
||||
raise Fatal('expected route or NSLIST but got %r' % line)
|
||||
subnets.append((
|
||||
int(family),
|
||||
@ -222,7 +233,7 @@ def main(method_name, syslog):
|
||||
break
|
||||
try:
|
||||
(family, ip) = line.strip().split(',', 1)
|
||||
except BaseException:
|
||||
except Exception:
|
||||
raise Fatal('expected nslist or PORTS but got %r' % line)
|
||||
nslist.append((int(family), ip))
|
||||
debug2('Got partial nslist: %r' % nslist)
|
||||
@ -239,14 +250,14 @@ def main(method_name, syslog):
|
||||
dnsport_v6 = int(ports[2])
|
||||
dnsport_v4 = int(ports[3])
|
||||
|
||||
assert(port_v6 >= 0)
|
||||
assert(port_v6 <= 65535)
|
||||
assert(port_v4 >= 0)
|
||||
assert(port_v4 <= 65535)
|
||||
assert(dnsport_v6 >= 0)
|
||||
assert(dnsport_v6 <= 65535)
|
||||
assert(dnsport_v4 >= 0)
|
||||
assert(dnsport_v4 <= 65535)
|
||||
assert port_v6 >= 0
|
||||
assert port_v6 <= 65535
|
||||
assert port_v4 >= 0
|
||||
assert port_v4 <= 65535
|
||||
assert dnsport_v6 >= 0
|
||||
assert dnsport_v6 <= 65535
|
||||
assert dnsport_v4 >= 0
|
||||
assert dnsport_v4 <= 65535
|
||||
|
||||
debug2('Got ports: %d,%d,%d,%d'
|
||||
% (port_v6, port_v4, dnsport_v6, dnsport_v4))
|
||||
@ -317,46 +328,46 @@ def main(method_name, syslog):
|
||||
finally:
|
||||
try:
|
||||
debug1('undoing changes.')
|
||||
except BaseException:
|
||||
except Exception:
|
||||
debug2('An error occurred, ignoring it.')
|
||||
|
||||
try:
|
||||
if subnets_v6 or nslist_v6:
|
||||
debug2('undoing IPv6 changes.')
|
||||
method.restore_firewall(port_v6, socket.AF_INET6, udp, user)
|
||||
except BaseException:
|
||||
except Exception:
|
||||
try:
|
||||
debug1("Error trying to undo IPv6 firewall.")
|
||||
debug1(traceback.format_exc())
|
||||
except BaseException:
|
||||
except Exception:
|
||||
debug2('An error occurred, ignoring it.')
|
||||
|
||||
try:
|
||||
if subnets_v4 or nslist_v4:
|
||||
debug2('undoing IPv4 changes.')
|
||||
method.restore_firewall(port_v4, socket.AF_INET, udp, user)
|
||||
except BaseException:
|
||||
except Exception:
|
||||
try:
|
||||
debug1("Error trying to undo IPv4 firewall.")
|
||||
debug1(traceback.format_exc())
|
||||
except BaseException:
|
||||
except Exception:
|
||||
debug2('An error occurred, ignoring it.')
|
||||
|
||||
try:
|
||||
# debug2() message printed in restore_etc_hosts() function.
|
||||
restore_etc_hosts(hostmap, port_v6 or port_v4)
|
||||
except BaseException:
|
||||
except Exception:
|
||||
try:
|
||||
debug1("Error trying to undo /etc/hosts changes.")
|
||||
debug1(traceback.format_exc())
|
||||
except BaseException:
|
||||
except Exception:
|
||||
debug2('An error occurred, ignoring it.')
|
||||
|
||||
try:
|
||||
flush_systemd_dns_cache()
|
||||
except BaseException:
|
||||
except Exception:
|
||||
try:
|
||||
debug1("Error trying to flush systemd dns cache.")
|
||||
debug1(traceback.format_exc())
|
||||
except BaseException:
|
||||
except Exception:
|
||||
debug2("An error occurred, ignoring it.")
|
||||
|
@ -55,7 +55,7 @@ def write_host_cache():
|
||||
|
||||
try:
|
||||
os.unlink(tmpname)
|
||||
except BaseException:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
|
@ -7,8 +7,6 @@ from sshuttle.helpers import Fatal, debug3
|
||||
|
||||
|
||||
def original_dst(sock):
|
||||
ip = "0.0.0.0"
|
||||
port = -1
|
||||
try:
|
||||
family = sock.family
|
||||
SO_ORIGINAL_DST = 80
|
||||
@ -72,8 +70,8 @@ class BaseMethod(object):
|
||||
|
||||
def send_udp(self, sock, srcip, dstip, data):
|
||||
if srcip is not None:
|
||||
Fatal("Method %s send_udp does not support setting srcip to %r"
|
||||
% (self.name, srcip))
|
||||
raise Fatal("Method %s send_udp does not support setting srcip to %r"
|
||||
% (self.name, srcip))
|
||||
sock.sendto(data, dstip)
|
||||
|
||||
def setup_tcp_listener(self, tcp_listener):
|
||||
|
@ -52,7 +52,7 @@ def _fill_oldctls(prefix):
|
||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=get_env())
|
||||
for line in p.stdout:
|
||||
line = line.decode()
|
||||
assert(line[-1] == '\n')
|
||||
assert line[-1] == '\n'
|
||||
(k, v) = line[:-1].split(': ', 1)
|
||||
_oldctls[k] = v.strip()
|
||||
rv = p.wait()
|
||||
@ -74,7 +74,7 @@ _changedctls = []
|
||||
|
||||
def sysctl_set(name, val, permanent=False):
|
||||
PREFIX = 'net.inet.ip'
|
||||
assert(name.startswith(PREFIX + '.'))
|
||||
assert name.startswith(PREFIX + '.')
|
||||
val = str(val)
|
||||
if not _oldctls:
|
||||
_fill_oldctls(PREFIX)
|
||||
|
@ -127,7 +127,7 @@ class Method(BaseMethod):
|
||||
|
||||
def _ipt_proto_ports(proto, fport, lport):
|
||||
return proto + ('--dport', '%d:%d' % (fport, lport)) \
|
||||
if fport else proto
|
||||
if fport else proto
|
||||
|
||||
mark_chain = 'sshuttle-m-%s' % port
|
||||
tproxy_chain = 'sshuttle-t-%s' % port
|
||||
|
@ -37,9 +37,9 @@ def parse_subnetport_file(s):
|
||||
def parse_subnetport(s):
|
||||
|
||||
if s.count(':') > 1:
|
||||
rx = r'(?:\[?([\w\:]+)(?:/(\d+))?]?)(?::(\d+)(?:-(\d+))?)?$'
|
||||
rx = r'(?:\[?(?:\*\.)?([\w\:]+)(?:/(\d+))?]?)(?::(\d+)(?:-(\d+))?)?$'
|
||||
else:
|
||||
rx = r'([\w\.\-]+)(?:/(\d+))?(?::(\d+)(?:-(\d+))?)?$'
|
||||
rx = r'((?:\*\.)?[\w\.\-]+)(?:/(\d+))?(?::(\d+)(?:-(\d+))?)?$'
|
||||
|
||||
m = re.match(rx, s)
|
||||
if not m:
|
||||
@ -396,18 +396,15 @@ parser.add_argument(
|
||||
(internal use only)
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
"--sudoers",
|
||||
action="store_true",
|
||||
help="""
|
||||
Add sshuttle to the sudoers for this user
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
"--sudoers-no-modify",
|
||||
action="store_true",
|
||||
help="""
|
||||
Prints the sudoers config to STDOUT and DOES NOT modify anything.
|
||||
Prints a sudo configuration to STDOUT which allows a user to
|
||||
run sshuttle without a password. This option is INSECURE because,
|
||||
with some cleverness, it also allows the user to run any command
|
||||
as root without a password. The output also includes a suggested
|
||||
method for you to install the configuration.
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
@ -415,16 +412,7 @@ parser.add_argument(
|
||||
default="",
|
||||
help="""
|
||||
Set the user name or group with %%group_name for passwordless operation.
|
||||
Default is the current user.set ALL for all users. Only works with
|
||||
--sudoers or --sudoers-no-modify option.
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
"--sudoers-filename",
|
||||
default="sshuttle_auto",
|
||||
help="""
|
||||
Set the file name for the sudoers.d file to be added. Default is
|
||||
"sshuttle_auto". Only works with --sudoers or --sudoers-no-modify option.
|
||||
Default is the current user. Only works with the --sudoers-no-modify option.
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
|
@ -34,7 +34,6 @@ def _ipmatch(ipstr):
|
||||
elif g[3] is None:
|
||||
ips += '.0'
|
||||
width = min(width, 24)
|
||||
ips = ips
|
||||
return (struct.unpack('!I', socket.inet_aton(ips))[0], width)
|
||||
|
||||
|
||||
@ -303,7 +302,7 @@ def main(latency_control, latency_buffer_size, auto_hosts, to_nameserver,
|
||||
hw.leftover = b('')
|
||||
|
||||
def hostwatch_ready(sock):
|
||||
assert(hw.pid)
|
||||
assert hw.pid
|
||||
content = hw.sock.recv(4096)
|
||||
if content:
|
||||
lines = (hw.leftover + content).split(b('\n'))
|
||||
@ -381,7 +380,7 @@ def main(latency_control, latency_buffer_size, auto_hosts, to_nameserver,
|
||||
|
||||
while mux.ok:
|
||||
if hw.pid:
|
||||
assert(hw.pid > 0)
|
||||
assert hw.pid > 0
|
||||
(rpid, rv) = os.waitpid(hw.pid, os.WNOHANG)
|
||||
if rpid:
|
||||
raise Fatal(
|
||||
|
@ -177,7 +177,7 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
|
||||
# it is present.
|
||||
pycmd = ("P=python3; $P -V 2>%s || P=python; "
|
||||
"exec \"$P\" -c %s; exit 97") % \
|
||||
(os.devnull, quote(pyscript))
|
||||
(os.devnull, quote(pyscript))
|
||||
pycmd = ("/bin/sh -c {}".format(quote(pycmd)))
|
||||
|
||||
if password is not None:
|
||||
|
@ -227,7 +227,7 @@ class SockWrapper:
|
||||
return 0
|
||||
|
||||
def write(self, buf):
|
||||
assert(buf)
|
||||
assert buf
|
||||
return self.uwrite(buf)
|
||||
|
||||
def uread(self):
|
||||
@ -402,15 +402,15 @@ class Mux(Handler):
|
||||
elif cmd == CMD_EXIT:
|
||||
self.ok = False
|
||||
elif cmd == CMD_TCP_CONNECT:
|
||||
assert(not self.channels.get(channel))
|
||||
assert not self.channels.get(channel)
|
||||
if self.new_channel:
|
||||
self.new_channel(channel, data)
|
||||
elif cmd == CMD_DNS_REQ:
|
||||
assert(not self.channels.get(channel))
|
||||
assert not self.channels.get(channel)
|
||||
if self.got_dns_req:
|
||||
self.got_dns_req(channel, data)
|
||||
elif cmd == CMD_UDP_OPEN:
|
||||
assert(not self.channels.get(channel))
|
||||
assert not self.channels.get(channel)
|
||||
if self.got_udp_open:
|
||||
self.got_udp_open(channel, data)
|
||||
elif cmd == CMD_ROUTES:
|
||||
@ -443,7 +443,7 @@ class Mux(Handler):
|
||||
# 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)
|
||||
fcntl.fcntl(self.wfile.fileno(), fcntl.F_SETFL, flags)
|
||||
if self.outbuf and self.outbuf[0]:
|
||||
wrote = _nb_clean(os.write, self.wfile.fileno(), self.outbuf[0])
|
||||
debug2('mux wrote: %r/%d' % (wrote, len(self.outbuf[0])))
|
||||
@ -459,7 +459,7 @@ class Mux(Handler):
|
||||
# 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)
|
||||
fcntl.fcntl(self.rfile.fileno(), fcntl.F_SETFL, flags)
|
||||
try:
|
||||
# If LATENCY_BUFFER_SIZE is inappropriately large, we will
|
||||
# get a MemoryError here. Read no more than 1MiB.
|
||||
@ -482,8 +482,8 @@ class Mux(Handler):
|
||||
if len(self.inbuf) >= (self.want or HDR_LEN):
|
||||
(s1, s2, channel, cmd, datalen) = \
|
||||
struct.unpack('!ccHHH', self.inbuf[:HDR_LEN])
|
||||
assert(s1 == b('S'))
|
||||
assert(s2 == b('S'))
|
||||
assert s1 == b('S')
|
||||
assert s2 == b('S')
|
||||
self.want = datalen + HDR_LEN
|
||||
if self.want and len(self.inbuf) >= self.want:
|
||||
data = self.inbuf[HDR_LEN:self.want]
|
||||
|
@ -1,89 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import socket
|
||||
import select
|
||||
import struct
|
||||
import time
|
||||
|
||||
listener = socket.socket()
|
||||
listener.bind(('127.0.0.1', 0))
|
||||
listener.listen(500)
|
||||
|
||||
servers = []
|
||||
clients = []
|
||||
remain = {}
|
||||
|
||||
NUMCLIENTS = 50
|
||||
count = 0
|
||||
|
||||
|
||||
while 1:
|
||||
if len(clients) < NUMCLIENTS:
|
||||
c = socket.socket()
|
||||
c.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
c.bind(('0.0.0.0', 0))
|
||||
c.connect(listener.getsockname())
|
||||
count += 1
|
||||
if count >= 16384:
|
||||
count = 1
|
||||
print('cli CREATING %d' % count)
|
||||
b = struct.pack('I', count) + 'x' * count
|
||||
remain[c] = count
|
||||
print('cli >> %r' % len(b))
|
||||
c.send(b)
|
||||
c.shutdown(socket.SHUT_WR)
|
||||
clients.append(c)
|
||||
r = [listener]
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
r = [listener] + servers + clients
|
||||
print('select(%d)' % len(r))
|
||||
r, w, x = select.select(r, [], [], 5)
|
||||
assert(r)
|
||||
for i in r:
|
||||
if i == listener:
|
||||
s, addr = listener.accept()
|
||||
servers.append(s)
|
||||
elif i in servers:
|
||||
b = i.recv(4096)
|
||||
print('srv << %r' % len(b))
|
||||
if i not in remain:
|
||||
assert(len(b) >= 4)
|
||||
want = struct.unpack('I', b[:4])[0]
|
||||
b = b[4:]
|
||||
# i.send('y'*want)
|
||||
else:
|
||||
want = remain[i]
|
||||
if want < len(b):
|
||||
print('weird wanted %d bytes, got %d: %r' % (want, len(b), b))
|
||||
assert(want >= len(b))
|
||||
want -= len(b)
|
||||
remain[i] = want
|
||||
if not b: # EOF
|
||||
if want:
|
||||
print('weird: eof but wanted %d more' % want)
|
||||
assert(want == 0)
|
||||
i.close()
|
||||
servers.remove(i)
|
||||
del remain[i]
|
||||
else:
|
||||
print('srv >> %r' % len(b))
|
||||
i.send('y' * len(b))
|
||||
if not want:
|
||||
i.shutdown(socket.SHUT_WR)
|
||||
elif i in clients:
|
||||
b = i.recv(4096)
|
||||
print('cli << %r' % len(b))
|
||||
want = remain[i]
|
||||
if want < len(b):
|
||||
print('weird wanted %d bytes, got %d: %r' % (want, len(b), b))
|
||||
assert(want >= len(b))
|
||||
want -= len(b)
|
||||
remain[i] = want
|
||||
if not b: # EOF
|
||||
if want:
|
||||
print('weird: eof but wanted %d more' % want)
|
||||
assert(want == 0)
|
||||
i.close()
|
||||
clients.remove(i)
|
||||
del remain[i]
|
||||
listener.accept()
|
@ -2,70 +2,45 @@ import os
|
||||
import sys
|
||||
import getpass
|
||||
from uuid import uuid4
|
||||
from subprocess import Popen, PIPE
|
||||
from sshuttle.helpers import log, debug1
|
||||
from distutils import spawn
|
||||
|
||||
path_to_sshuttle = sys.argv[0]
|
||||
path_to_dist_packages = os.path.dirname(os.path.abspath(__file__))[:-9]
|
||||
|
||||
# randomize command alias to avoid collisions
|
||||
command_alias = 'SSHUTTLE%(num)s' % {'num': uuid4().hex[-3:].upper()}
|
||||
def build_config(user_name):
|
||||
template = '''
|
||||
# WARNING: If you intend to restrict a user to only running the
|
||||
# sshuttle command as root, THIS CONFIGURATION IS INSECURE.
|
||||
# When a user can run sshuttle as root (with or without a password),
|
||||
# they can also run other commands as root because sshuttle itself
|
||||
# can run a command specified by the user with the --ssh-cmd option.
|
||||
|
||||
# INSTRUCTIONS: Add this text to your sudo configuration to run
|
||||
# sshuttle without needing to enter a sudo password. To use this
|
||||
# configuration, run 'visudo /etc/sudoers.d/sshuttle_auto' as root and
|
||||
# paste this text into the editor that it opens. If you want to give
|
||||
# multiple users these privileges, you may wish to use use different
|
||||
# filenames for each one (i.e., /etc/sudoers.d/sshuttle_auto_john).
|
||||
|
||||
# This configuration was initially generated by the
|
||||
# 'sshuttle --sudoers-no-modify' command.
|
||||
|
||||
# Template for the sudoers file
|
||||
template = '''
|
||||
Cmnd_Alias %(ca)s = /usr/bin/env PYTHONPATH=%(dist_packages)s %(py)s %(path)s *
|
||||
|
||||
%(user_name)s ALL=NOPASSWD: %(ca)s
|
||||
'''
|
||||
|
||||
warning_msg = "# WARNING: When you allow a user to run sshuttle as root,\n" \
|
||||
"# they can then use sshuttle's --ssh-cmd option to run any\n" \
|
||||
"# command as root.\n"
|
||||
|
||||
|
||||
def build_config(user_name):
|
||||
content = warning_msg
|
||||
content += template % {
|
||||
'ca': command_alias,
|
||||
'dist_packages': path_to_dist_packages,
|
||||
content = template % {
|
||||
# randomize command alias to avoid collisions
|
||||
'ca': 'SSHUTTLE%(num)s' % {'num': uuid4().hex[-3:].upper()},
|
||||
'dist_packages': os.path.dirname(os.path.abspath(__file__))[:-9],
|
||||
'py': sys.executable,
|
||||
'path': path_to_sshuttle,
|
||||
'path': sys.argv[0],
|
||||
'user_name': user_name,
|
||||
}
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def save_config(content, file_name):
|
||||
process = Popen([
|
||||
'/usr/bin/sudo',
|
||||
spawn.find_executable('sudoers-add'),
|
||||
file_name,
|
||||
], stdout=PIPE, stdin=PIPE)
|
||||
|
||||
process.stdin.write(content.encode())
|
||||
|
||||
streamdata = process.communicate()[0]
|
||||
sys.stdout.write(streamdata.decode("ASCII"))
|
||||
returncode = process.returncode
|
||||
|
||||
if returncode:
|
||||
log('Failed updating sudoers file.')
|
||||
debug1(streamdata)
|
||||
exit(returncode)
|
||||
else:
|
||||
log('Success, sudoers file update.')
|
||||
exit(0)
|
||||
|
||||
|
||||
def sudoers(user_name=None, no_modify=None, file_name=None):
|
||||
def sudoers(user_name=None):
|
||||
user_name = user_name or getpass.getuser()
|
||||
content = build_config(user_name)
|
||||
|
||||
if no_modify:
|
||||
sys.stdout.write(content)
|
||||
exit(0)
|
||||
else:
|
||||
sys.stdout.write(warning_msg)
|
||||
save_config(content, file_name)
|
||||
sys.stdout.write(content)
|
||||
exit(0)
|
||||
|
1
sshuttle/version.py
Normal file
1
sshuttle/version.py
Normal file
@ -0,0 +1 @@
|
||||
__version__ = version = '1.1.1'
|
@ -1,7 +1,11 @@
|
||||
import io
|
||||
import os
|
||||
from socket import AF_INET, AF_INET6
|
||||
|
||||
from unittest.mock import Mock, patch, call
|
||||
|
||||
import pytest
|
||||
|
||||
import sshuttle.firewall
|
||||
|
||||
|
||||
@ -59,6 +63,21 @@ def test_rewrite_etc_hosts(tmpdir):
|
||||
assert orig_hosts.computehash() == new_hosts.computehash()
|
||||
|
||||
|
||||
@patch('os.link')
|
||||
@patch('os.rename')
|
||||
def test_rewrite_etc_hosts_no_overwrite(mock_link, mock_rename, tmpdir):
|
||||
mock_link.side_effect = OSError
|
||||
mock_rename.side_effect = OSError
|
||||
|
||||
with pytest.raises(OSError):
|
||||
os.link('/test_from', '/test_to')
|
||||
|
||||
with pytest.raises(OSError):
|
||||
os.rename('/test_from', '/test_to')
|
||||
|
||||
test_rewrite_etc_hosts(tmpdir)
|
||||
|
||||
|
||||
def test_subnet_weight():
|
||||
subnets = [
|
||||
(AF_INET, 16, 0, '192.168.0.0', 0, 0),
|
||||
@ -123,7 +142,7 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
|
||||
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||
AF_INET6,
|
||||
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0),
|
||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
|
||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
|
||||
True,
|
||||
None,
|
||||
'0x01'),
|
||||
@ -132,7 +151,7 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
|
||||
[(AF_INET, u'1.2.3.33')],
|
||||
AF_INET,
|
||||
[(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
|
||||
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||
True,
|
||||
None,
|
||||
'0x01'),
|
||||
|
@ -192,5 +192,4 @@ def test_family_ip_tuple():
|
||||
def test_family_to_string():
|
||||
assert sshuttle.helpers.family_to_string(AF_INET) == "AF_INET"
|
||||
assert sshuttle.helpers.family_to_string(AF_INET6) == "AF_INET6"
|
||||
expected = 'AddressFamily.AF_UNIX'
|
||||
assert sshuttle.helpers.family_to_string(socket.AF_UNIX) == expected
|
||||
assert isinstance(sshuttle.helpers.family_to_string(socket.AF_UNIX), str)
|
||||
|
@ -81,7 +81,7 @@ def test_assert_features():
|
||||
|
||||
def test_firewall_command():
|
||||
method = get_method('nat')
|
||||
assert not method.firewall_command("somthing")
|
||||
assert not method.firewall_command("something")
|
||||
|
||||
|
||||
@patch('sshuttle.methods.nat.ipt')
|
||||
|
@ -92,7 +92,7 @@ def test_assert_features():
|
||||
@patch('sshuttle.methods.pf.pf_get_dev')
|
||||
def test_firewall_command_darwin(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
method = get_method('pf')
|
||||
assert not method.firewall_command("somthing")
|
||||
assert not method.firewall_command("something")
|
||||
|
||||
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
||||
AF_INET, socket.IPPROTO_TCP,
|
||||
@ -115,7 +115,7 @@ def test_firewall_command_darwin(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
@patch('sshuttle.methods.pf.pf_get_dev')
|
||||
def test_firewall_command_freebsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
method = get_method('pf')
|
||||
assert not method.firewall_command("somthing")
|
||||
assert not method.firewall_command("something")
|
||||
|
||||
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
||||
AF_INET, socket.IPPROTO_TCP,
|
||||
@ -138,7 +138,7 @@ def test_firewall_command_freebsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
@patch('sshuttle.methods.pf.pf_get_dev')
|
||||
def test_firewall_command_openbsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
method = get_method('pf')
|
||||
assert not method.firewall_command("somthing")
|
||||
assert not method.firewall_command("something")
|
||||
|
||||
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
||||
AF_INET, socket.IPPROTO_TCP,
|
||||
|
@ -78,7 +78,7 @@ def test_assert_features():
|
||||
|
||||
def test_firewall_command():
|
||||
method = get_method('tproxy')
|
||||
assert not method.firewall_command("somthing")
|
||||
assert not method.firewall_command("something")
|
||||
|
||||
|
||||
@patch('sshuttle.methods.tproxy.ipt')
|
||||
|
@ -1,5 +1,6 @@
|
||||
import socket
|
||||
from argparse import ArgumentTypeError as Fatal
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
@ -27,6 +28,23 @@ _ip6_reprs = {
|
||||
_ip6_swidths = (48, 64, 96, 115, 128)
|
||||
|
||||
|
||||
def _mock_getaddrinfo(host, *_):
|
||||
return {
|
||||
"example.com": [
|
||||
(socket.AF_INET6, socket.SOCK_STREAM, 0, '', ('2606:2800:220:1:248:1893:25c8:1946', 0, 0, 0)),
|
||||
(socket.AF_INET, socket.SOCK_STREAM, 0, '', ('93.184.216.34', 0)),
|
||||
],
|
||||
"my.local": [
|
||||
(socket.AF_INET6, socket.SOCK_STREAM, 0, '', ('::1', 0, 0, 0)),
|
||||
(socket.AF_INET, socket.SOCK_STREAM, 0, '', ('127.0.0.1', 0)),
|
||||
],
|
||||
"*.blogspot.com": [
|
||||
(socket.AF_INET6, socket.SOCK_STREAM, 0, '', ('2404:6800:4004:821::2001', 0, 0, 0)),
|
||||
(socket.AF_INET, socket.SOCK_STREAM, 0, '', ('142.251.42.129', 0)),
|
||||
],
|
||||
}.get(host, [])
|
||||
|
||||
|
||||
def test_parse_subnetport_ip4():
|
||||
for ip_repr, ip in _ip4_reprs.items():
|
||||
assert sshuttle.options.parse_subnetport(ip_repr) \
|
||||
@ -105,3 +123,56 @@ def test_parse_subnetport_ip6_with_mask_and_port():
|
||||
def test_convert_arg_line_to_args_skips_comments():
|
||||
parser = sshuttle.options.MyArgumentParser()
|
||||
assert parser.convert_arg_line_to_args("# whatever something") == []
|
||||
|
||||
|
||||
@patch('sshuttle.options.socket.getaddrinfo', side_effect=_mock_getaddrinfo)
|
||||
def test_parse_subnetport_host(mock_getaddrinfo):
|
||||
assert set(sshuttle.options.parse_subnetport('example.com')) \
|
||||
== set([
|
||||
(socket.AF_INET6, '2606:2800:220:1:248:1893:25c8:1946', 128, 0, 0),
|
||||
(socket.AF_INET, '93.184.216.34', 32, 0, 0),
|
||||
])
|
||||
assert set(sshuttle.options.parse_subnetport('my.local')) \
|
||||
== set([
|
||||
(socket.AF_INET6, '::1', 128, 0, 0),
|
||||
(socket.AF_INET, '127.0.0.1', 32, 0, 0),
|
||||
])
|
||||
assert set(sshuttle.options.parse_subnetport('*.blogspot.com')) \
|
||||
== set([
|
||||
(socket.AF_INET6, '2404:6800:4004:821::2001', 128, 0, 0),
|
||||
(socket.AF_INET, '142.251.42.129', 32, 0, 0),
|
||||
])
|
||||
|
||||
|
||||
@patch('sshuttle.options.socket.getaddrinfo', side_effect=_mock_getaddrinfo)
|
||||
def test_parse_subnetport_host_with_port(mock_getaddrinfo):
|
||||
assert set(sshuttle.options.parse_subnetport('example.com:80')) \
|
||||
== set([
|
||||
(socket.AF_INET6, '2606:2800:220:1:248:1893:25c8:1946', 128, 80, 80),
|
||||
(socket.AF_INET, '93.184.216.34', 32, 80, 80),
|
||||
])
|
||||
assert set(sshuttle.options.parse_subnetport('example.com:80-90')) \
|
||||
== set([
|
||||
(socket.AF_INET6, '2606:2800:220:1:248:1893:25c8:1946', 128, 80, 90),
|
||||
(socket.AF_INET, '93.184.216.34', 32, 80, 90),
|
||||
])
|
||||
assert set(sshuttle.options.parse_subnetport('my.local:445')) \
|
||||
== set([
|
||||
(socket.AF_INET6, '::1', 128, 445, 445),
|
||||
(socket.AF_INET, '127.0.0.1', 32, 445, 445),
|
||||
])
|
||||
assert set(sshuttle.options.parse_subnetport('my.local:445-450')) \
|
||||
== set([
|
||||
(socket.AF_INET6, '::1', 128, 445, 450),
|
||||
(socket.AF_INET, '127.0.0.1', 32, 445, 450),
|
||||
])
|
||||
assert set(sshuttle.options.parse_subnetport('*.blogspot.com:80')) \
|
||||
== set([
|
||||
(socket.AF_INET6, '2404:6800:4004:821::2001', 128, 80, 80),
|
||||
(socket.AF_INET, '142.251.42.129', 32, 80, 80),
|
||||
])
|
||||
assert set(sshuttle.options.parse_subnetport('*.blogspot.com:80-90')) \
|
||||
== set([
|
||||
(socket.AF_INET6, '2404:6800:4004:821::2001', 128, 80, 90),
|
||||
(socket.AF_INET, '142.251.42.129', 32, 80, 90),
|
||||
])
|
||||
|
Reference in New Issue
Block a user