mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-07-06 01:30:32 +02:00
Compare commits
127 Commits
Author | SHA1 | Date | |
---|---|---|---|
fd424c5c55 | |||
e6563f2c39 | |||
dd037dd8ef | |||
89bd3fc2f3 | |||
5c479220a7 | |||
32d0054455 | |||
b2a29d3b22 | |||
9b831499d7 | |||
e4ae714cf8 | |||
152c14c079 | |||
a604d107ef | |||
b4e4680ef4 | |||
59b6777f01 | |||
ef804e7cdb | |||
67b4499c52 | |||
e53c0df411 | |||
794b14eaac | |||
670cc363ba | |||
6f70519dc1 | |||
efb7d1f6cc | |||
031fb4d053 | |||
3e80464626 | |||
399d389af6 | |||
c2ddaa0bcf | |||
cec87a5341 | |||
0ddebdeee6 | |||
3c3f5de672 | |||
9f718e8632 | |||
3abc3d2a1a | |||
5b9f438d42 | |||
998e5c5849 | |||
7c140daf07 | |||
755e522eff | |||
6b7cf80420 | |||
ac06e7968f | |||
f597e70ae6 | |||
802c6f5a6e | |||
17bfdc24b8 | |||
4e592265f6 | |||
a289580f24 | |||
799c9f33d0 | |||
5778437148 | |||
dffc1c7f92 | |||
25cd95130d | |||
a54fd8ab4e | |||
d336002833 | |||
fd8a0b624d | |||
e0ef2964cd | |||
faf34e14e0 | |||
23207f27fa | |||
7edc7ba7bc | |||
8ba8dff719 | |||
57111d7a13 | |||
f23b24b74e | |||
b8e6ebf741 | |||
53da036879 | |||
ad05994e65 | |||
e704ea74e5 | |||
d99940c58e | |||
1d240e0cd9 | |||
060f849c7e | |||
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@v4
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v3
|
||||||
|
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@v3
|
||||||
|
|
||||||
|
# ℹ️ 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@v3
|
6
.github/workflows/pythonpackage.yml
vendored
6
.github/workflows/pythonpackage.yml
vendored
@ -17,12 +17,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
|
python-version: ["3.8", "3.9", "3.10"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2.3.1
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,5 +1,5 @@
|
|||||||
/sshuttle/version.py
|
|
||||||
/tmp/
|
/tmp/
|
||||||
|
/.coverage
|
||||||
/.cache/
|
/.cache/
|
||||||
/.eggs/
|
/.eggs/
|
||||||
/.tox/
|
/.tox/
|
||||||
@ -15,4 +15,6 @@
|
|||||||
/.redo
|
/.redo
|
||||||
/.pytest_cache/
|
/.pytest_cache/
|
||||||
/.python-version
|
/.python-version
|
||||||
.vscode/
|
/.direnv/
|
||||||
|
/result
|
||||||
|
/.vscode/
|
||||||
|
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@ -0,0 +1 @@
|
|||||||
|
python 3.10.6
|
@ -73,7 +73,11 @@ Obtaining sshuttle
|
|||||||
# ports
|
# ports
|
||||||
cd /usr/ports/net/py-sshuttle && make install clean
|
cd /usr/ports/net/py-sshuttle && make install clean
|
||||||
# pkg
|
# pkg
|
||||||
pkg install py36-sshuttle
|
pkg install py39-sshuttle
|
||||||
|
|
||||||
|
- OpenBSD::
|
||||||
|
|
||||||
|
pkg_add sshuttle
|
||||||
|
|
||||||
- macOS, via MacPorts::
|
- macOS, via MacPorts::
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
|
|
@ -103,7 +103,7 @@ pygments_style = 'sphinx'
|
|||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
html_theme = 'default'
|
html_theme = 'furo'
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
|
@ -19,6 +19,5 @@ Installation
|
|||||||
Optionally after 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
|
.. option:: --disable-ipv6
|
||||||
|
|
||||||
Disable IPv6 support for methods that support it (nft, tproxy, and
|
Disable IPv6 support for methods that support it (nat, nft,
|
||||||
pf).
|
tproxy, and pf).
|
||||||
|
|
||||||
.. option:: --firewall
|
.. option:: --firewall
|
||||||
|
|
||||||
@ -262,28 +262,23 @@ Options
|
|||||||
makes it a lot easier to debug and test the :option:`--auto-hosts`
|
makes it a lot easier to debug and test the :option:`--auto-hosts`
|
||||||
feature.
|
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
|
.. option:: --sudoers-no-modify
|
||||||
|
|
||||||
sshuttle will auto generate the proper sudoers.d config and print it to
|
sshuttle prints a configuration to stdout which allows a user to
|
||||||
stdout. The option will not modify the system at all.
|
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
|
.. option:: --sudoers-user
|
||||||
|
|
||||||
Set the user name or group with %group_name for passwordless operation.
|
Set the user name or group with %group_name for passwordless
|
||||||
Default is the current user.set ALL for all users. Only works with
|
operation. Default is the current user. Set to ALL for all users
|
||||||
--sudoers or --sudoers-no-modify option.
|
(NOT RECOMMENDED: See note about security in --sudoers-no-modify
|
||||||
|
documentation above). Only works with the --sudoers-no-modify
|
||||||
.. option:: --sudoers-filename
|
option.
|
||||||
|
|
||||||
Set the file name for the sudoers.d file to be added. Default is
|
|
||||||
"sshuttle_auto". Only works with --sudoers.
|
|
||||||
|
|
||||||
.. option:: -t <mark>, --tmark=<mark>
|
.. option:: -t <mark>, --tmark=<mark>
|
||||||
|
|
||||||
@ -326,6 +321,18 @@ annotations. For example::
|
|||||||
192.168.63.0/24
|
192.168.63.0/24
|
||||||
|
|
||||||
|
|
||||||
|
Environment Variable
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
You can specify command line options with the `SSHUTTLE_ARGS` environment
|
||||||
|
variable. If a given option is defined in both the environment variable and
|
||||||
|
command line, the value on the command line will take precedence.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
SSHUTTLE_ARGS="-e 'ssh -v' --dns" sshuttle -r example.com 0/0
|
||||||
|
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@ -460,7 +467,7 @@ Packet-level forwarding (eg. using the tun/tap devices on
|
|||||||
Linux) seems elegant at first, but it results in
|
Linux) seems elegant at first, but it results in
|
||||||
several problems, notably the 'tcp over tcp' problem. The
|
several problems, notably the 'tcp over tcp' problem. The
|
||||||
tcp protocol depends fundamentally on packets being dropped
|
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
|
you pass tcp packets through a tcp-based tunnel (such as
|
||||||
ssh), the inner tcp packets will never be dropped, and so
|
ssh), the inner tcp packets will never be dropped, and so
|
||||||
the inner tcp stream's congestion control will be
|
the inner tcp stream's congestion control will be
|
||||||
|
@ -6,7 +6,7 @@ Client side Requirements
|
|||||||
|
|
||||||
- sudo, or root access on your client machine.
|
- sudo, or root access on your client machine.
|
||||||
(The server doesn't need admin access.)
|
(The server doesn't need admin access.)
|
||||||
- Python 3.6 or greater.
|
- Python 3.8 or greater.
|
||||||
|
|
||||||
|
|
||||||
Linux with NAT method
|
Linux with NAT method
|
||||||
@ -72,7 +72,7 @@ cmd.exe with Administrator access. See :doc:`windows` for more information.
|
|||||||
Server side Requirements
|
Server side Requirements
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
- Python 3.6 or greater.
|
- Python 3.8 or greater.
|
||||||
|
|
||||||
|
|
||||||
Additional Suggested Software
|
Additional Suggested Software
|
||||||
|
@ -11,7 +11,7 @@ Forward all traffic::
|
|||||||
sshuttle -r username@sshserver 0.0.0.0/0
|
sshuttle -r username@sshserver 0.0.0.0/0
|
||||||
|
|
||||||
- Use the :option:`sshuttle -r` parameter to specify a remote server.
|
- Use the :option:`sshuttle -r` parameter to specify a remote server.
|
||||||
One some systems, you may also need to use the :option:`sshuttle -x`
|
On some systems, you may also need to use the :option:`sshuttle -x`
|
||||||
parameter to exclude sshserver or sshserver:22 so that your local
|
parameter to exclude sshserver or sshserver:22 so that your local
|
||||||
machine can communicate directly to sshserver without it being
|
machine can communicate directly to sshserver without it being
|
||||||
redirected by sshuttle.
|
redirected by sshuttle.
|
||||||
@ -71,44 +71,23 @@ admin access on the server.
|
|||||||
|
|
||||||
Sudoers File
|
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
|
To print a sudo configuration file and see a suggested way to install it, run::
|
||||||
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::
|
|
||||||
|
|
||||||
sshuttle --sudoers-no-modify
|
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 prepended with `%` and sudoers user
|
||||||
|
aliases will work. See the sudoers manual for more information on valid
|
||||||
|
user-specified actions. The option must be used with `--sudoers-no-modify`::
|
||||||
|
|
||||||
08:40 PM william$ sshuttle --sudoers-no-modify
|
sshuttle --sudoers-no-modify --sudoers-user mike
|
||||||
|
sshuttle --sudoers-no-modify --sudoers-user %sudo
|
||||||
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
|
|
||||||
|
175
flake.lock
generated
Normal file
175
flake.lock
generated
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705309234,
|
||||||
|
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1694529238,
|
||||||
|
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix-github-actions": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"poetry2nix",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1698974481,
|
||||||
|
"narHash": "sha256-yPncV9Ohdz1zPZxYHQf47S8S0VrnhV7nNhCawY46hDA=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nix-github-actions",
|
||||||
|
"rev": "4bb5e752616262457bc7ca5882192a564c0472d2",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nix-github-actions",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1708161998,
|
||||||
|
"narHash": "sha256-6KnemmUorCvlcAvGziFosAVkrlWZGIc6UNT9GUYr0jQ=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "84d981bae8b5e783b3b548de505b22880559515f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-23.11",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"poetry2nix": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
|
"nix-github-actions": "nix-github-actions",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"systems": "systems_3",
|
||||||
|
"treefmt-nix": "treefmt-nix"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1708175019,
|
||||||
|
"narHash": "sha256-B7wY2pNrLc3X9uYRo1LUmVzI6oH6fX8oi+96GdUpayU=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "poetry2nix",
|
||||||
|
"rev": "403d923ea8e2e6cedce3a0f04a9394c4244cb806",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "poetry2nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"poetry2nix": "poetry2nix"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "systems",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"treefmt-nix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"poetry2nix",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1699786194,
|
||||||
|
"narHash": "sha256-3h3EH1FXQkIeAuzaWB+nK0XK54uSD46pp+dMD3gAcB4=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"rev": "e82f32aa7f06bbbd56d7b12186d555223dc399d1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
42
flake.nix
Normal file
42
flake.nix
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
description =
|
||||||
|
"Transparent proxy server that works as a poor man's VPN. Forwards over ssh. Doesn't require admin. Works with Linux and MacOS. Supports DNS tunneling.";
|
||||||
|
|
||||||
|
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
|
||||||
|
inputs.poetry2nix = {
|
||||||
|
url = "github:nix-community/poetry2nix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils, poetry2nix }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
p2n = import poetry2nix { inherit pkgs; };
|
||||||
|
overrides = p2n.defaultPoetryOverrides.extend (self: super: {
|
||||||
|
nh3 = super.nh3.override { preferWheel = true; };
|
||||||
|
bump2version = super.bump2version.overridePythonAttrs (old: {
|
||||||
|
buildInputs = (old.buildInputs or [ ]) ++ [ super.setuptools ];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
poetry_env = p2n.mkPoetryEnv {
|
||||||
|
python = pkgs.python3;
|
||||||
|
projectDir = self;
|
||||||
|
inherit overrides;
|
||||||
|
};
|
||||||
|
poetry_app = p2n.mkPoetryApplication {
|
||||||
|
python = pkgs.python3;
|
||||||
|
projectDir = self;
|
||||||
|
inherit overrides;
|
||||||
|
};
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in {
|
||||||
|
packages = {
|
||||||
|
sshuttle = poetry_app;
|
||||||
|
default = self.packages.${system}.sshuttle;
|
||||||
|
};
|
||||||
|
devShells.default =
|
||||||
|
pkgs.mkShell { packages = [ pkgs.poetry poetry_env ]; };
|
||||||
|
});
|
||||||
|
}
|
848
poetry.lock
generated
Normal file
848
poetry.lock
generated
Normal file
@ -0,0 +1,848 @@
|
|||||||
|
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bump2version"
|
||||||
|
version = "1.0.1"
|
||||||
|
description = "Version-bump your software with a single command!"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410"},
|
||||||
|
{file = "bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2024.2.2"
|
||||||
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
|
||||||
|
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cffi"
|
||||||
|
version = "1.16.0"
|
||||||
|
description = "Foreign Function Interface for Python calling C code."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
|
||||||
|
{file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
|
||||||
|
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
|
||||||
|
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
|
||||||
|
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
|
||||||
|
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
|
||||||
|
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
|
||||||
|
{file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
|
||||||
|
{file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
|
||||||
|
{file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
|
||||||
|
{file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
|
||||||
|
{file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
|
||||||
|
{file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
|
||||||
|
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
|
||||||
|
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
|
||||||
|
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
|
||||||
|
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
|
||||||
|
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
|
||||||
|
{file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
|
||||||
|
{file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
|
||||||
|
{file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
|
||||||
|
{file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
|
||||||
|
{file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
|
||||||
|
{file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
|
||||||
|
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
|
||||||
|
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
|
||||||
|
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
|
||||||
|
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
|
||||||
|
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
|
||||||
|
{file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
|
||||||
|
{file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
|
||||||
|
{file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
|
||||||
|
{file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
|
||||||
|
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
|
||||||
|
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
|
||||||
|
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
|
||||||
|
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
|
||||||
|
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
|
||||||
|
{file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
|
||||||
|
{file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
|
||||||
|
{file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
|
||||||
|
{file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
|
||||||
|
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
|
||||||
|
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
|
||||||
|
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
|
||||||
|
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
|
||||||
|
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
|
||||||
|
{file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
|
||||||
|
{file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
|
||||||
|
{file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
|
||||||
|
{file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
|
||||||
|
{file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pycparser = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "charset-normalizer"
|
||||||
|
version = "3.3.2"
|
||||||
|
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7.0"
|
||||||
|
files = [
|
||||||
|
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
|
||||||
|
{file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
|
||||||
|
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
description = "Cross-platform colored terminal text."
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
files = [
|
||||||
|
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||||
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.4.1"
|
||||||
|
description = "Code coverage measurement for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"},
|
||||||
|
{file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"},
|
||||||
|
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"},
|
||||||
|
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"},
|
||||||
|
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"},
|
||||||
|
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"},
|
||||||
|
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"},
|
||||||
|
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"},
|
||||||
|
{file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"},
|
||||||
|
{file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"},
|
||||||
|
{file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"},
|
||||||
|
{file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"},
|
||||||
|
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"},
|
||||||
|
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"},
|
||||||
|
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"},
|
||||||
|
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"},
|
||||||
|
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"},
|
||||||
|
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"},
|
||||||
|
{file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"},
|
||||||
|
{file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"},
|
||||||
|
{file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"},
|
||||||
|
{file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"},
|
||||||
|
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"},
|
||||||
|
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"},
|
||||||
|
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"},
|
||||||
|
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"},
|
||||||
|
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"},
|
||||||
|
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"},
|
||||||
|
{file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"},
|
||||||
|
{file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"},
|
||||||
|
{file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"},
|
||||||
|
{file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"},
|
||||||
|
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"},
|
||||||
|
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"},
|
||||||
|
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"},
|
||||||
|
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"},
|
||||||
|
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"},
|
||||||
|
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"},
|
||||||
|
{file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"},
|
||||||
|
{file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"},
|
||||||
|
{file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"},
|
||||||
|
{file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"},
|
||||||
|
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"},
|
||||||
|
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"},
|
||||||
|
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"},
|
||||||
|
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"},
|
||||||
|
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"},
|
||||||
|
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"},
|
||||||
|
{file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"},
|
||||||
|
{file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"},
|
||||||
|
{file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"},
|
||||||
|
{file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
toml = ["tomli"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cryptography"
|
||||||
|
version = "42.0.3"
|
||||||
|
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "cryptography-42.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:de5086cd475d67113ccb6f9fae6d8fe3ac54a4f9238fd08bfdb07b03d791ff0a"},
|
||||||
|
{file = "cryptography-42.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:935cca25d35dda9e7bd46a24831dfd255307c55a07ff38fd1a92119cffc34857"},
|
||||||
|
{file = "cryptography-42.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20100c22b298c9eaebe4f0b9032ea97186ac2555f426c3e70670f2517989543b"},
|
||||||
|
{file = "cryptography-42.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eb6368d5327d6455f20327fb6159b97538820355ec00f8cc9464d617caecead"},
|
||||||
|
{file = "cryptography-42.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:39d5c93e95bcbc4c06313fc6a500cee414ee39b616b55320c1904760ad686938"},
|
||||||
|
{file = "cryptography-42.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d96ea47ce6d0055d5b97e761d37b4e84195485cb5a38401be341fabf23bc32a"},
|
||||||
|
{file = "cryptography-42.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d1998e545081da0ab276bcb4b33cce85f775adb86a516e8f55b3dac87f469548"},
|
||||||
|
{file = "cryptography-42.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93fbee08c48e63d5d1b39ab56fd3fdd02e6c2431c3da0f4edaf54954744c718f"},
|
||||||
|
{file = "cryptography-42.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:90147dad8c22d64b2ff7331f8d4cddfdc3ee93e4879796f837bdbb2a0b141e0c"},
|
||||||
|
{file = "cryptography-42.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4dcab7c25e48fc09a73c3e463d09ac902a932a0f8d0c568238b3696d06bf377b"},
|
||||||
|
{file = "cryptography-42.0.3-cp37-abi3-win32.whl", hash = "sha256:1e935c2900fb53d31f491c0de04f41110351377be19d83d908c1fd502ae8daa5"},
|
||||||
|
{file = "cryptography-42.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:762f3771ae40e111d78d77cbe9c1035e886ac04a234d3ee0856bf4ecb3749d54"},
|
||||||
|
{file = "cryptography-42.0.3-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3ec384058b642f7fb7e7bff9664030011ed1af8f852540c76a1317a9dd0d20"},
|
||||||
|
{file = "cryptography-42.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35772a6cffd1f59b85cb670f12faba05513446f80352fe811689b4e439b5d89e"},
|
||||||
|
{file = "cryptography-42.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04859aa7f12c2b5f7e22d25198ddd537391f1695df7057c8700f71f26f47a129"},
|
||||||
|
{file = "cryptography-42.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3d1f5a1d403a8e640fa0887e9f7087331abb3f33b0f2207d2cc7f213e4a864c"},
|
||||||
|
{file = "cryptography-42.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df34312149b495d9d03492ce97471234fd9037aa5ba217c2a6ea890e9166f151"},
|
||||||
|
{file = "cryptography-42.0.3-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:de4ae486041878dc46e571a4c70ba337ed5233a1344c14a0790c4c4be4bbb8b4"},
|
||||||
|
{file = "cryptography-42.0.3-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0fab2a5c479b360e5e0ea9f654bcebb535e3aa1e493a715b13244f4e07ea8eec"},
|
||||||
|
{file = "cryptography-42.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25b09b73db78facdfd7dd0fa77a3f19e94896197c86e9f6dc16bce7b37a96504"},
|
||||||
|
{file = "cryptography-42.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d5cf11bc7f0b71fb71af26af396c83dfd3f6eed56d4b6ef95d57867bf1e4ba65"},
|
||||||
|
{file = "cryptography-42.0.3-cp39-abi3-win32.whl", hash = "sha256:0fea01527d4fb22ffe38cd98951c9044400f6eff4788cf52ae116e27d30a1ba3"},
|
||||||
|
{file = "cryptography-42.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:2619487f37da18d6826e27854a7f9d4d013c51eafb066c80d09c63cf24505306"},
|
||||||
|
{file = "cryptography-42.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ead69ba488f806fe1b1b4050febafdbf206b81fa476126f3e16110c818bac396"},
|
||||||
|
{file = "cryptography-42.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:20180da1b508f4aefc101cebc14c57043a02b355d1a652b6e8e537967f1e1b46"},
|
||||||
|
{file = "cryptography-42.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fbf0f3f0fac7c089308bd771d2c6c7b7d53ae909dce1db52d8e921f6c19bb3a"},
|
||||||
|
{file = "cryptography-42.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c23f03cfd7d9826cdcbad7850de67e18b4654179e01fe9bc623d37c2638eb4ef"},
|
||||||
|
{file = "cryptography-42.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db0480ffbfb1193ac4e1e88239f31314fe4c6cdcf9c0b8712b55414afbf80db4"},
|
||||||
|
{file = "cryptography-42.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:6c25e1e9c2ce682d01fc5e2dde6598f7313027343bd14f4049b82ad0402e52cd"},
|
||||||
|
{file = "cryptography-42.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9541c69c62d7446539f2c1c06d7046aef822940d248fa4b8962ff0302862cc1f"},
|
||||||
|
{file = "cryptography-42.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b797099d221df7cce5ff2a1d272761d1554ddf9a987d3e11f6459b38cd300fd"},
|
||||||
|
{file = "cryptography-42.0.3.tar.gz", hash = "sha256:069d2ce9be5526a44093a0991c450fe9906cdf069e0e7cd67d9dee49a62b9ebe"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
|
||||||
|
docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||||
|
nox = ["nox"]
|
||||||
|
pep8test = ["check-sdist", "click", "mypy", "ruff"]
|
||||||
|
sdist = ["build"]
|
||||||
|
ssh = ["bcrypt (>=3.1.5)"]
|
||||||
|
test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
|
||||||
|
test-randomorder = ["pytest-randomly"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "docutils"
|
||||||
|
version = "0.20.1"
|
||||||
|
description = "Docutils -- Python Documentation Utilities"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"},
|
||||||
|
{file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exceptiongroup"
|
||||||
|
version = "1.2.0"
|
||||||
|
description = "Backport of PEP 654 (exception groups)"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
|
||||||
|
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["pytest (>=6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flake8"
|
||||||
|
version = "7.0.0"
|
||||||
|
description = "the modular source code checker: pep8 pyflakes and co"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8.1"
|
||||||
|
files = [
|
||||||
|
{file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"},
|
||||||
|
{file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
mccabe = ">=0.7.0,<0.8.0"
|
||||||
|
pycodestyle = ">=2.11.0,<2.12.0"
|
||||||
|
pyflakes = ">=3.2.0,<3.3.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.6"
|
||||||
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
|
||||||
|
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "importlib-metadata"
|
||||||
|
version = "7.0.1"
|
||||||
|
description = "Read metadata from Python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"},
|
||||||
|
{file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
zipp = ">=0.5"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||||
|
perf = ["ipython"]
|
||||||
|
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "brain-dead simple config-ini parsing"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||||
|
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jaraco-classes"
|
||||||
|
version = "3.3.1"
|
||||||
|
description = "Utility functions for Python class constructs"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "jaraco.classes-3.3.1-py3-none-any.whl", hash = "sha256:86b534de565381f6b3c1c830d13f931d7be1a75f0081c57dff615578676e2206"},
|
||||||
|
{file = "jaraco.classes-3.3.1.tar.gz", hash = "sha256:cb28a5ebda8bc47d8c8015307d93163464f9f2b91ab4006e09ff0ce07e8bfb30"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
more-itertools = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||||
|
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jeepney"
|
||||||
|
version = "0.8.0"
|
||||||
|
description = "Low-level, pure Python DBus protocol wrapper."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"},
|
||||||
|
{file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"]
|
||||||
|
trio = ["async_generator", "trio"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "keyring"
|
||||||
|
version = "24.3.0"
|
||||||
|
description = "Store and access your passwords safely."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "keyring-24.3.0-py3-none-any.whl", hash = "sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836"},
|
||||||
|
{file = "keyring-24.3.0.tar.gz", hash = "sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""}
|
||||||
|
"jaraco.classes" = "*"
|
||||||
|
jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""}
|
||||||
|
pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""}
|
||||||
|
SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
completion = ["shtab (>=1.1.0)"]
|
||||||
|
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||||
|
testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown-it-py"
|
||||||
|
version = "3.0.0"
|
||||||
|
description = "Python port of markdown-it. Markdown parsing, done right!"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
|
||||||
|
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
mdurl = ">=0.1,<1.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
|
||||||
|
code-style = ["pre-commit (>=3.0,<4.0)"]
|
||||||
|
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
|
||||||
|
linkify = ["linkify-it-py (>=1,<3)"]
|
||||||
|
plugins = ["mdit-py-plugins"]
|
||||||
|
profiling = ["gprof2dot"]
|
||||||
|
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
|
||||||
|
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mccabe"
|
||||||
|
version = "0.7.0"
|
||||||
|
description = "McCabe checker, plugin for flake8"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
|
||||||
|
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mdurl"
|
||||||
|
version = "0.1.2"
|
||||||
|
description = "Markdown URL utilities"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
|
||||||
|
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "more-itertools"
|
||||||
|
version = "10.2.0"
|
||||||
|
description = "More routines for operating on iterables, beyond itertools"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"},
|
||||||
|
{file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nh3"
|
||||||
|
version = "0.2.15"
|
||||||
|
description = "Python bindings to the ammonia HTML sanitization library."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9c0d415f6b7f2338f93035bba5c0d8c1b464e538bfbb1d598acd47d7969284f0"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6f42f99f0cf6312e470b6c09e04da31f9abaadcd3eb591d7d1a88ea931dca7f3"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac19c0d68cd42ecd7ead91a3a032fdfff23d29302dbb1311e641a130dfefba97"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0d77272ce6d34db6c87b4f894f037d55183d9518f948bba236fe81e2bb4e28"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8d595df02413aa38586c24811237e95937ef18304e108b7e92c890a06793e3bf"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86e447a63ca0b16318deb62498db4f76fc60699ce0a1231262880b38b6cff911"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3277481293b868b2715907310c7be0f1b9d10491d5adf9fce11756a97e97eddf"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60684857cfa8fdbb74daa867e5cad3f0c9789415aba660614fe16cd66cbb9ec7"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b803a5875e7234907f7d64777dfde2b93db992376f3d6d7af7f3bc347deb305"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d02d0ff79dfd8208ed25a39c12cbda092388fff7f1662466e27d97ad011b770"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f3b53ba93bb7725acab1e030bc2ecd012a817040fd7851b332f86e2f9bb98dc6"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:b1e97221cedaf15a54f5243f2c5894bb12ca951ae4ddfd02a9d4ea9df9e1a29d"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5167a6403d19c515217b6bcaaa9be420974a6ac30e0da9e84d4fc67a5d474c5"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-win32.whl", hash = "sha256:427fecbb1031db085eaac9931362adf4a796428ef0163070c484b5a768e71601"},
|
||||||
|
{file = "nh3-0.2.15-cp37-abi3-win_amd64.whl", hash = "sha256:bc2d086fb540d0fa52ce35afaded4ea526b8fc4d3339f783db55c95de40ef02e"},
|
||||||
|
{file = "nh3-0.2.15.tar.gz", hash = "sha256:d1e30ff2d8d58fb2a14961f7aac1bbb1c51f9bdd7da727be35c63826060b0bf3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "23.2"
|
||||||
|
description = "Core utilities for Python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
|
||||||
|
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkginfo"
|
||||||
|
version = "1.9.6"
|
||||||
|
description = "Query metadata from sdists / bdists / installed packages."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"},
|
||||||
|
{file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
testing = ["pytest", "pytest-cov"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.4.0"
|
||||||
|
description = "plugin and hook calling mechanisms for python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
|
||||||
|
{file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pre-commit", "tox"]
|
||||||
|
testing = ["pytest", "pytest-benchmark"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycodestyle"
|
||||||
|
version = "2.11.1"
|
||||||
|
description = "Python style guide checker"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"},
|
||||||
|
{file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycparser"
|
||||||
|
version = "2.21"
|
||||||
|
description = "C parser in Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
files = [
|
||||||
|
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
|
||||||
|
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyflakes"
|
||||||
|
version = "3.2.0"
|
||||||
|
description = "passive checker of Python programs"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"},
|
||||||
|
{file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.17.2"
|
||||||
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
|
||||||
|
{file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
plugins = ["importlib-metadata"]
|
||||||
|
windows-terminal = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "8.0.1"
|
||||||
|
description = "pytest: simple powerful testing with Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pytest-8.0.1-py3-none-any.whl", hash = "sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca"},
|
||||||
|
{file = "pytest-8.0.1.tar.gz", hash = "sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
|
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||||
|
iniconfig = "*"
|
||||||
|
packaging = "*"
|
||||||
|
pluggy = ">=1.3.0,<2.0"
|
||||||
|
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "4.1.0"
|
||||||
|
description = "Pytest plugin for measuring coverage."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
|
||||||
|
{file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||||
|
pytest = ">=4.6"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pywin32-ctypes"
|
||||||
|
version = "0.2.2"
|
||||||
|
description = "A (partial) reimplementation of pywin32 using ctypes/cffi"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"},
|
||||||
|
{file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "readme-renderer"
|
||||||
|
version = "42.0"
|
||||||
|
description = "readme_renderer is a library for rendering readme descriptions for Warehouse"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "readme_renderer-42.0-py3-none-any.whl", hash = "sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d"},
|
||||||
|
{file = "readme_renderer-42.0.tar.gz", hash = "sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
docutils = ">=0.13.1"
|
||||||
|
nh3 = ">=0.2.14"
|
||||||
|
Pygments = ">=2.5.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
md = ["cmarkgfm (>=0.8.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests"
|
||||||
|
version = "2.31.0"
|
||||||
|
description = "Python HTTP for Humans."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
|
||||||
|
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
certifi = ">=2017.4.17"
|
||||||
|
charset-normalizer = ">=2,<4"
|
||||||
|
idna = ">=2.5,<4"
|
||||||
|
urllib3 = ">=1.21.1,<3"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||||
|
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests-toolbelt"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "A utility belt for advanced users of python-requests"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
files = [
|
||||||
|
{file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"},
|
||||||
|
{file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
requests = ">=2.0.1,<3.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rfc3986"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "Validating URI References per RFC 3986"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"},
|
||||||
|
{file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
idna2008 = ["idna"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rich"
|
||||||
|
version = "13.7.0"
|
||||||
|
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7.0"
|
||||||
|
files = [
|
||||||
|
{file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"},
|
||||||
|
{file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
markdown-it-py = ">=2.2.0"
|
||||||
|
pygments = ">=2.13.0,<3.0.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "secretstorage"
|
||||||
|
version = "3.3.3"
|
||||||
|
description = "Python bindings to FreeDesktop.org Secret Service API"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"},
|
||||||
|
{file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cryptography = ">=2.0"
|
||||||
|
jeepney = ">=0.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tomli"
|
||||||
|
version = "2.0.1"
|
||||||
|
description = "A lil' TOML parser"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||||
|
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "twine"
|
||||||
|
version = "5.0.0"
|
||||||
|
description = "Collection of utilities for publishing packages on PyPI"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "twine-5.0.0-py3-none-any.whl", hash = "sha256:a262933de0b484c53408f9edae2e7821c1c45a3314ff2df9bdd343aa7ab8edc0"},
|
||||||
|
{file = "twine-5.0.0.tar.gz", hash = "sha256:89b0cc7d370a4b66421cc6102f269aa910fe0f1861c124f573cf2ddedbc10cf4"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
importlib-metadata = ">=3.6"
|
||||||
|
keyring = ">=15.1"
|
||||||
|
pkginfo = ">=1.8.1"
|
||||||
|
readme-renderer = ">=35.0"
|
||||||
|
requests = ">=2.20"
|
||||||
|
requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0"
|
||||||
|
rfc3986 = ">=1.4.0"
|
||||||
|
rich = ">=12.0.0"
|
||||||
|
urllib3 = ">=1.26.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urllib3"
|
||||||
|
version = "2.2.1"
|
||||||
|
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"},
|
||||||
|
{file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
|
||||||
|
h2 = ["h2 (>=4,<5)"]
|
||||||
|
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||||
|
zstd = ["zstandard (>=0.18.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zipp"
|
||||||
|
version = "3.17.0"
|
||||||
|
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
|
||||||
|
{file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||||
|
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "2.0"
|
||||||
|
python-versions = "^3.10"
|
||||||
|
content-hash = "e089c5db68017b943468ee1860df31baf7ec88633ce8aaaa25e82df84ed59a4d"
|
25
pyproject.toml
Normal file
25
pyproject.toml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "sshuttle"
|
||||||
|
version = "1.1.2"
|
||||||
|
description = "Transparent proxy server that works as a poor man's VPN. Forwards over ssh. Doesn't require admin. Works with Linux and MacOS. Supports DNS tunneling."
|
||||||
|
authors = ["Brian May <brian@linuxpenguins.xyz>"]
|
||||||
|
license = "LGPL-2.1"
|
||||||
|
readme = "README.rst"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.10"
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
pytest = "^8.0.1"
|
||||||
|
pytest-cov = "^4.1.0"
|
||||||
|
flake8 = "^7.0.0"
|
||||||
|
pyflakes = "^3.2.0"
|
||||||
|
bump2version = "^1.0.1"
|
||||||
|
twine = "^5.0.0"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
sshuttle = "sshuttle.cmdline:main"
|
@ -1,5 +1,5 @@
|
|||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
pytest==6.2.5
|
pytest==8.0.0
|
||||||
pytest-cov==3.0.0
|
pytest-cov==4.1.0
|
||||||
flake8==4.0.1
|
flake8==7.0.0
|
||||||
pyflakes==2.4.0
|
bump2version==1.0.1
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
setuptools-scm==6.4.2
|
Sphinx==7.1.2
|
||||||
Sphinx==4.3.2
|
furo==2024.1.29
|
||||||
|
22
setup.cfg
22
setup.cfg
@ -1,17 +1,27 @@
|
|||||||
|
[bumpversion]
|
||||||
|
current_version = 1.1.2
|
||||||
|
|
||||||
|
[bumpversion:file:setup.py]
|
||||||
|
|
||||||
|
[bumpversion:file:pyproject.toml]
|
||||||
|
|
||||||
|
[bumpversion:file:sshuttle/version.py]
|
||||||
|
|
||||||
[aliases]
|
[aliases]
|
||||||
test=pytest
|
test = pytest
|
||||||
|
|
||||||
[bdist_wheel]
|
[bdist_wheel]
|
||||||
universal = 1
|
universal = 1
|
||||||
|
|
||||||
[upload]
|
[upload]
|
||||||
sign=true
|
sign = true
|
||||||
identity=0x1784577F811F6EAC
|
identity = 0x1784577F811F6EAC
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
count=true
|
count = true
|
||||||
show-source=true
|
show-source = true
|
||||||
statistics=true
|
statistics = true
|
||||||
|
max-line-length = 128
|
||||||
|
|
||||||
[tool:pytest]
|
[tool:pytest]
|
||||||
addopts = --cov=sshuttle --cov-branch --cov-report=term-missing
|
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
|
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(
|
setup(
|
||||||
name="sshuttle",
|
name="sshuttle",
|
||||||
use_scm_version={
|
version='1.1.2',
|
||||||
'write_to': "sshuttle/version.py",
|
|
||||||
'version_scheme': version_scheme,
|
|
||||||
},
|
|
||||||
setup_requires=['setuptools_scm'],
|
|
||||||
# version=version,
|
|
||||||
url='https://github.com/sshuttle/sshuttle',
|
url='https://github.com/sshuttle/sshuttle',
|
||||||
author='Brian May',
|
author='Brian May',
|
||||||
author_email='brian@linuxpenguins.xyz',
|
author_email='brian@linuxpenguins.xyz',
|
||||||
@ -46,22 +35,20 @@ setup(
|
|||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"Intended Audience :: End Users/Desktop",
|
"Intended Audience :: End Users/Desktop",
|
||||||
"License :: OSI Approved :: "
|
"License :: OSI Approved :: " +
|
||||||
"GNU Lesser General Public License v2 or later (LGPLv2+)",
|
"GNU Lesser General Public License v2 or later (LGPLv2+)",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python :: 3.6",
|
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
"Topic :: System :: Networking",
|
"Topic :: System :: Networking",
|
||||||
],
|
],
|
||||||
scripts=['bin/sudoers-add'],
|
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
'sshuttle = sshuttle.cmdline:main',
|
'sshuttle = sshuttle.cmdline:main',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
python_requires='>=3.6',
|
python_requires='>=3.8',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
],
|
],
|
||||||
tests_require=[
|
tests_require=[
|
||||||
|
@ -18,7 +18,7 @@ while 1:
|
|||||||
name = name.decode("ASCII")
|
name = name.decode("ASCII")
|
||||||
nbytes = int(sys.stdin.readline())
|
nbytes = int(sys.stdin.readline())
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
sys.stderr.write(' s: assembling %r (%d bytes)\r\n'
|
sys.stderr.write(' s: assembling %r (%d bytes)\n'
|
||||||
% (name, nbytes))
|
% (name, nbytes))
|
||||||
content = z.decompress(sys.stdin.read(nbytes))
|
content = z.decompress(sys.stdin.read(nbytes))
|
||||||
|
|
||||||
|
@ -21,6 +21,10 @@ try:
|
|||||||
from pwd import getpwnam
|
from pwd import getpwnam
|
||||||
except ImportError:
|
except ImportError:
|
||||||
getpwnam = None
|
getpwnam = None
|
||||||
|
try:
|
||||||
|
from grp import getgrnam
|
||||||
|
except ImportError:
|
||||||
|
getgrnam = None
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
@ -123,14 +127,14 @@ class MultiListener:
|
|||||||
self.bind_called = False
|
self.bind_called = False
|
||||||
|
|
||||||
def setsockopt(self, level, optname, value):
|
def setsockopt(self, level, optname, value):
|
||||||
assert(self.bind_called)
|
assert self.bind_called
|
||||||
if self.v6:
|
if self.v6:
|
||||||
self.v6.setsockopt(level, optname, value)
|
self.v6.setsockopt(level, optname, value)
|
||||||
if self.v4:
|
if self.v4:
|
||||||
self.v4.setsockopt(level, optname, value)
|
self.v4.setsockopt(level, optname, value)
|
||||||
|
|
||||||
def add_handler(self, handlers, callback, method, mux):
|
def add_handler(self, handlers, callback, method, mux):
|
||||||
assert(self.bind_called)
|
assert self.bind_called
|
||||||
socks = []
|
socks = []
|
||||||
if self.v6:
|
if self.v6:
|
||||||
socks.append(self.v6)
|
socks.append(self.v6)
|
||||||
@ -145,7 +149,7 @@ class MultiListener:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def listen(self, backlog):
|
def listen(self, backlog):
|
||||||
assert(self.bind_called)
|
assert self.bind_called
|
||||||
if self.v6:
|
if self.v6:
|
||||||
self.v6.listen(backlog)
|
self.v6.listen(backlog)
|
||||||
if self.v4:
|
if self.v4:
|
||||||
@ -160,11 +164,26 @@ class MultiListener:
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
def bind(self, address_v6, address_v4):
|
def bind(self, address_v6, address_v4):
|
||||||
assert(not self.bind_called)
|
assert not self.bind_called
|
||||||
self.bind_called = True
|
self.bind_called = True
|
||||||
if address_v6 is not None:
|
if address_v6 is not None:
|
||||||
self.v6 = socket.socket(socket.AF_INET6, self.type, self.proto)
|
self.v6 = socket.socket(socket.AF_INET6, self.type, self.proto)
|
||||||
|
try:
|
||||||
self.v6.bind(address_v6)
|
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:
|
else:
|
||||||
self.v6 = None
|
self.v6 = None
|
||||||
if address_v4 is not None:
|
if address_v4 is not None:
|
||||||
@ -174,7 +193,7 @@ class MultiListener:
|
|||||||
self.v4 = None
|
self.v4 = None
|
||||||
|
|
||||||
def print_listening(self, what):
|
def print_listening(self, what):
|
||||||
assert(self.bind_called)
|
assert self.bind_called
|
||||||
if self.v6:
|
if self.v6:
|
||||||
listenip = self.v6.getsockname()
|
listenip = self.v6.getsockname()
|
||||||
debug1('%s listening on %r.' % (what, listenip))
|
debug1('%s listening on %r.' % (what, listenip))
|
||||||
@ -205,8 +224,8 @@ class FirewallClient:
|
|||||||
else:
|
else:
|
||||||
# Linux typically uses sudo; OpenBSD uses doas. However, some
|
# Linux typically uses sudo; OpenBSD uses doas. However, some
|
||||||
# Linux distributions are starting to use doas.
|
# Linux distributions are starting to use doas.
|
||||||
sudo_cmd = ['sudo', '-p', '[local sudo] Password: ']+argvbase
|
sudo_cmd = ['sudo', '-p', '[local sudo] Password: ']
|
||||||
doas_cmd = ['doas']+argvbase
|
doas_cmd = ['doas']
|
||||||
|
|
||||||
# For clarity, try to replace executable name with the
|
# For clarity, try to replace executable name with the
|
||||||
# full path.
|
# full path.
|
||||||
@ -225,8 +244,13 @@ class FirewallClient:
|
|||||||
pp_prefix = ['/usr/bin/env',
|
pp_prefix = ['/usr/bin/env',
|
||||||
'PYTHONPATH=%s' %
|
'PYTHONPATH=%s' %
|
||||||
os.path.dirname(os.path.dirname(__file__))]
|
os.path.dirname(os.path.dirname(__file__))]
|
||||||
sudo_cmd = pp_prefix + sudo_cmd
|
sudo_cmd = sudo_cmd + pp_prefix
|
||||||
doas_cmd = pp_prefix + doas_cmd
|
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
|
# If we can find doas and not sudo or if we are on
|
||||||
# OpenBSD, try using doas first.
|
# OpenBSD, try using doas first.
|
||||||
@ -254,7 +278,8 @@ class FirewallClient:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
debug1("Starting firewall manager with command: %r" % argv)
|
debug1("Starting firewall manager with command: %r" % argv)
|
||||||
self.p = ssubprocess.Popen(argv, stdout=s1, preexec_fn=setup)
|
self.p = ssubprocess.Popen(argv, stdout=s1, stdin=s1,
|
||||||
|
preexec_fn=setup)
|
||||||
# No env: Talking to `FirewallClient.start`, which has no i18n.
|
# No env: Talking to `FirewallClient.start`, which has no i18n.
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
# This exception will occur if the program isn't
|
# This exception will occur if the program isn't
|
||||||
@ -278,10 +303,28 @@ class FirewallClient:
|
|||||||
'%r returned %d' % (self.argv, rv))
|
'%r returned %d' % (self.argv, rv))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Normally, READY will be the first text on the first
|
||||||
|
# line. However, if an administrator replaced sudo with a
|
||||||
|
# shell script that echos a message to stdout and then
|
||||||
|
# runs sudo, READY won't be on the first line. To
|
||||||
|
# workaround this problem, we read a limited number of
|
||||||
|
# lines until we encounter "READY". Store all of the text
|
||||||
|
# we skipped in case we need it for an error message.
|
||||||
|
#
|
||||||
|
# A proper way to print a sudo warning message is to use
|
||||||
|
# sudo's lecture feature. sshuttle works correctly without
|
||||||
|
# this hack if sudo's lecture feature is used instead.
|
||||||
|
skipped_text = line
|
||||||
|
for i in range(100):
|
||||||
|
if line[0:5] == b'READY':
|
||||||
|
break
|
||||||
|
line = self.pfile.readline()
|
||||||
|
skipped_text += line
|
||||||
|
|
||||||
if line[0:5] != b'READY':
|
if line[0:5] != b'READY':
|
||||||
debug1('Unable to start firewall manager. '
|
debug1('Unable to start firewall manager. '
|
||||||
'Expected READY, got %r. '
|
'Expected READY, got %r. '
|
||||||
'Command=%r' % (line, self.argv))
|
'Command=%r' % (skipped_text, self.argv))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
method_name = line[6:-1]
|
method_name = line[6:-1]
|
||||||
@ -295,7 +338,7 @@ class FirewallClient:
|
|||||||
|
|
||||||
def setup(self, subnets_include, subnets_exclude, nslist,
|
def setup(self, subnets_include, subnets_exclude, nslist,
|
||||||
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp,
|
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp,
|
||||||
user, tmark):
|
user, group, tmark):
|
||||||
self.subnets_include = subnets_include
|
self.subnets_include = subnets_include
|
||||||
self.subnets_exclude = subnets_exclude
|
self.subnets_exclude = subnets_exclude
|
||||||
self.nslist = nslist
|
self.nslist = nslist
|
||||||
@ -305,6 +348,7 @@ class FirewallClient:
|
|||||||
self.dnsport_v4 = dnsport_v4
|
self.dnsport_v4 = dnsport_v4
|
||||||
self.udp = udp
|
self.udp = udp
|
||||||
self.user = user
|
self.user = user
|
||||||
|
self.group = group
|
||||||
self.tmark = tmark
|
self.tmark = tmark
|
||||||
|
|
||||||
def check(self):
|
def check(self):
|
||||||
@ -343,9 +387,14 @@ class FirewallClient:
|
|||||||
user = bytes(self.user, 'utf-8')
|
user = bytes(self.user, 'utf-8')
|
||||||
else:
|
else:
|
||||||
user = b'%d' % self.user
|
user = b'%d' % self.user
|
||||||
|
if self.group is None:
|
||||||
self.pfile.write(b'GO %d %s %s %d\n' %
|
group = b'-'
|
||||||
(udp, user, bytes(self.tmark, 'ascii'), os.getpid()))
|
elif isinstance(self.group, str):
|
||||||
|
group = bytes(self.group, 'utf-8')
|
||||||
|
else:
|
||||||
|
group = b'%d' % self.group
|
||||||
|
self.pfile.write(b'GO %d %s %s %s %d\n' %
|
||||||
|
(udp, user, group, bytes(self.tmark, 'ascii'), os.getpid()))
|
||||||
self.pfile.flush()
|
self.pfile.flush()
|
||||||
|
|
||||||
line = self.pfile.readline()
|
line = self.pfile.readline()
|
||||||
@ -354,8 +403,8 @@ class FirewallClient:
|
|||||||
raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
|
raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
|
||||||
|
|
||||||
def sethostip(self, hostname, ip):
|
def sethostip(self, hostname, ip):
|
||||||
assert(not re.search(br'[^-\w\.]', hostname))
|
assert not re.search(br'[^-\w\.]', hostname)
|
||||||
assert(not re.search(br'[^0-9.]', ip))
|
assert not re.search(br'[^0-9.]', ip)
|
||||||
self.pfile.write(b'HOST %s,%s\n' % (hostname, ip))
|
self.pfile.write(b'HOST %s,%s\n' % (hostname, ip))
|
||||||
self.pfile.flush()
|
self.pfile.flush()
|
||||||
|
|
||||||
@ -706,7 +755,7 @@ def main(listenip_v6, listenip_v4,
|
|||||||
latency_buffer_size, dns, nslist,
|
latency_buffer_size, dns, nslist,
|
||||||
method_name, seed_hosts, auto_hosts, auto_nets,
|
method_name, seed_hosts, auto_hosts, auto_nets,
|
||||||
subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
|
subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
|
||||||
user, sudo_pythonpath, tmark):
|
user, group, sudo_pythonpath, tmark):
|
||||||
|
|
||||||
if not remotename:
|
if not remotename:
|
||||||
raise Fatal("You must use -r/--remote to specify a remote "
|
raise Fatal("You must use -r/--remote to specify a remote "
|
||||||
@ -809,6 +858,15 @@ def main(listenip_v6, listenip_v4,
|
|||||||
raise Fatal("User %s does not exist." % user)
|
raise Fatal("User %s does not exist." % user)
|
||||||
required.user = False if user is None else True
|
required.user = False if user is None else True
|
||||||
|
|
||||||
|
if group is not None:
|
||||||
|
if getgrnam is None:
|
||||||
|
raise Fatal("Routing by group not available on this system.")
|
||||||
|
try:
|
||||||
|
group = getgrnam(group).gr_gid
|
||||||
|
except KeyError:
|
||||||
|
raise Fatal("Group %s does not exist." % user)
|
||||||
|
required.group = False if group is None else True
|
||||||
|
|
||||||
if not required.ipv6 and len(subnets_v6) > 0:
|
if not required.ipv6 and len(subnets_v6) > 0:
|
||||||
print("WARNING: IPv6 subnets were ignored because IPv6 is disabled "
|
print("WARNING: IPv6 subnets were ignored because IPv6 is disabled "
|
||||||
"in sshuttle.")
|
"in sshuttle.")
|
||||||
@ -953,7 +1011,7 @@ def main(listenip_v6, listenip_v4,
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
if not bound:
|
if not bound:
|
||||||
assert(last_e)
|
assert last_e
|
||||||
raise last_e
|
raise last_e
|
||||||
tcp_listener.listen(10)
|
tcp_listener.listen(10)
|
||||||
tcp_listener.print_listening("TCP redirector")
|
tcp_listener.print_listening("TCP redirector")
|
||||||
@ -999,7 +1057,7 @@ def main(listenip_v6, listenip_v4,
|
|||||||
|
|
||||||
dns_listener.print_listening("DNS")
|
dns_listener.print_listening("DNS")
|
||||||
if not bound:
|
if not bound:
|
||||||
assert(last_e)
|
assert last_e
|
||||||
raise last_e
|
raise last_e
|
||||||
else:
|
else:
|
||||||
dnsport_v6 = 0
|
dnsport_v6 = 0
|
||||||
@ -1038,7 +1096,7 @@ def main(listenip_v6, listenip_v4,
|
|||||||
# start the firewall
|
# start the firewall
|
||||||
fw.setup(subnets_include, subnets_exclude, nslist,
|
fw.setup(subnets_include, subnets_exclude, nslist,
|
||||||
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
|
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
|
||||||
required.udp, user, tmark)
|
required.udp, user, group, tmark)
|
||||||
|
|
||||||
# start the client process
|
# start the client process
|
||||||
try:
|
try:
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shlex
|
||||||
import socket
|
import socket
|
||||||
import platform
|
import sys
|
||||||
import sshuttle.helpers as helpers
|
import sshuttle.helpers as helpers
|
||||||
import sshuttle.client as client
|
import sshuttle.client as client
|
||||||
import sshuttle.firewall as firewall
|
import sshuttle.firewall as firewall
|
||||||
@ -12,22 +14,17 @@ from sshuttle.sudoers import sudoers
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
opt = parser.parse_args()
|
if 'SSHUTTLE_ARGS' in os.environ:
|
||||||
|
env_args = shlex.split(os.environ['SSHUTTLE_ARGS'])
|
||||||
|
else:
|
||||||
|
env_args = []
|
||||||
|
args = [*env_args, *sys.argv[1:]]
|
||||||
|
|
||||||
if opt.sudoers or opt.sudoers_no_modify:
|
opt = parser.parse_args(args)
|
||||||
if platform.platform().startswith('OpenBSD'):
|
|
||||||
log('Automatic sudoers does not work on BSD')
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if not opt.sudoers_filename:
|
if opt.sudoers_no_modify:
|
||||||
log('--sudoers-file must be set or omitted.')
|
# sudoers() calls exit() when it completes
|
||||||
return 1
|
sudoers(user_name=opt.sudoers_user)
|
||||||
|
|
||||||
sudoers(
|
|
||||||
user_name=opt.sudoers_user,
|
|
||||||
no_modify=opt.sudoers_no_modify,
|
|
||||||
file_name=opt.sudoers_filename
|
|
||||||
)
|
|
||||||
|
|
||||||
if opt.daemon:
|
if opt.daemon:
|
||||||
opt.syslog = 1
|
opt.syslog = 1
|
||||||
@ -45,7 +42,8 @@ def main():
|
|||||||
parser.error('exactly zero arguments expected')
|
parser.error('exactly zero arguments expected')
|
||||||
return firewall.main(opt.method, opt.syslog)
|
return firewall.main(opt.method, opt.syslog)
|
||||||
elif opt.hostwatch:
|
elif opt.hostwatch:
|
||||||
return hostwatch.hw_main(opt.subnets, opt.auto_hosts)
|
hostwatch.hw_main(opt.subnets, opt.auto_hosts)
|
||||||
|
return 0
|
||||||
else:
|
else:
|
||||||
# parse_subnetports() is used to create a list of includes
|
# parse_subnetports() is used to create a list of includes
|
||||||
# and excludes. It is called once for each parameter and
|
# and excludes. It is called once for each parameter and
|
||||||
@ -115,6 +113,7 @@ def main():
|
|||||||
opt.to_ns,
|
opt.to_ns,
|
||||||
opt.pidfile,
|
opt.pidfile,
|
||||||
opt.user,
|
opt.user,
|
||||||
|
opt.group,
|
||||||
opt.sudo_pythonpath,
|
opt.sudo_pythonpath,
|
||||||
opt.tmark)
|
opt.tmark)
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import errno
|
import errno
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
@ -30,7 +31,11 @@ def rewrite_etc_hosts(hostmap, port):
|
|||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
if old_content.strip() and not os.path.exists(BAKFILE):
|
if old_content.strip() and not os.path.exists(BAKFILE):
|
||||||
|
try:
|
||||||
os.link(HOSTSFILE, BAKFILE)
|
os.link(HOSTSFILE, BAKFILE)
|
||||||
|
except OSError:
|
||||||
|
# file is locked - performing non-atomic copy
|
||||||
|
shutil.copyfile(HOSTSFILE, BAKFILE)
|
||||||
tmpname = "%s.%d.tmp" % (HOSTSFILE, port)
|
tmpname = "%s.%d.tmp" % (HOSTSFILE, port)
|
||||||
f = open(tmpname, 'w')
|
f = open(tmpname, 'w')
|
||||||
for line in old_content.rstrip().split('\n'):
|
for line in old_content.rstrip().split('\n'):
|
||||||
@ -46,8 +51,14 @@ def rewrite_etc_hosts(hostmap, port):
|
|||||||
os.chmod(tmpname, st.st_mode)
|
os.chmod(tmpname, st.st_mode)
|
||||||
else:
|
else:
|
||||||
os.chown(tmpname, 0, 0)
|
os.chown(tmpname, 0, 0)
|
||||||
os.chmod(tmpname, 0o600)
|
os.chmod(tmpname, 0o644)
|
||||||
|
try:
|
||||||
os.rename(tmpname, HOSTSFILE)
|
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):
|
def restore_etc_hosts(hostmap, port):
|
||||||
@ -99,11 +110,6 @@ def setup_daemon():
|
|||||||
# setsid() fails if sudo is configured with the use_pty option.
|
# setsid() fails if sudo is configured with the use_pty option.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# because of limitations of the 'su' command, the *real* stdin/stdout
|
|
||||||
# are both attached to stdout initially. Clone stdout into stdin so we
|
|
||||||
# can read from it.
|
|
||||||
os.dup2(1, 0)
|
|
||||||
|
|
||||||
return sys.stdin, sys.stdout
|
return sys.stdin, sys.stdout
|
||||||
|
|
||||||
|
|
||||||
@ -200,7 +206,7 @@ def main(method_name, syslog):
|
|||||||
try:
|
try:
|
||||||
(family, width, exclude, ip, fport, lport) = \
|
(family, width, exclude, ip, fport, lport) = \
|
||||||
line.strip().split(',', 5)
|
line.strip().split(',', 5)
|
||||||
except BaseException:
|
except Exception:
|
||||||
raise Fatal('expected route or NSLIST but got %r' % line)
|
raise Fatal('expected route or NSLIST but got %r' % line)
|
||||||
subnets.append((
|
subnets.append((
|
||||||
int(family),
|
int(family),
|
||||||
@ -222,7 +228,7 @@ def main(method_name, syslog):
|
|||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
(family, ip) = line.strip().split(',', 1)
|
(family, ip) = line.strip().split(',', 1)
|
||||||
except BaseException:
|
except Exception:
|
||||||
raise Fatal('expected nslist or PORTS but got %r' % line)
|
raise Fatal('expected nslist or PORTS but got %r' % line)
|
||||||
nslist.append((int(family), ip))
|
nslist.append((int(family), ip))
|
||||||
debug2('Got partial nslist: %r' % nslist)
|
debug2('Got partial nslist: %r' % nslist)
|
||||||
@ -239,14 +245,14 @@ def main(method_name, syslog):
|
|||||||
dnsport_v6 = int(ports[2])
|
dnsport_v6 = int(ports[2])
|
||||||
dnsport_v4 = int(ports[3])
|
dnsport_v4 = int(ports[3])
|
||||||
|
|
||||||
assert(port_v6 >= 0)
|
assert port_v6 >= 0
|
||||||
assert(port_v6 <= 65535)
|
assert port_v6 <= 65535
|
||||||
assert(port_v4 >= 0)
|
assert port_v4 >= 0
|
||||||
assert(port_v4 <= 65535)
|
assert port_v4 <= 65535
|
||||||
assert(dnsport_v6 >= 0)
|
assert dnsport_v6 >= 0
|
||||||
assert(dnsport_v6 <= 65535)
|
assert dnsport_v6 <= 65535
|
||||||
assert(dnsport_v4 >= 0)
|
assert dnsport_v4 >= 0
|
||||||
assert(dnsport_v4 <= 65535)
|
assert dnsport_v4 <= 65535
|
||||||
|
|
||||||
debug2('Got ports: %d,%d,%d,%d'
|
debug2('Got ports: %d,%d,%d,%d'
|
||||||
% (port_v6, port_v4, dnsport_v6, dnsport_v4))
|
% (port_v6, port_v4, dnsport_v6, dnsport_v4))
|
||||||
@ -259,13 +265,15 @@ def main(method_name, syslog):
|
|||||||
|
|
||||||
_, _, args = line.partition(" ")
|
_, _, args = line.partition(" ")
|
||||||
global sshuttle_pid
|
global sshuttle_pid
|
||||||
udp, user, tmark, sshuttle_pid = args.strip().split(" ", 3)
|
udp, user, group, tmark, sshuttle_pid = args.strip().split(" ", 4)
|
||||||
udp = bool(int(udp))
|
udp = bool(int(udp))
|
||||||
sshuttle_pid = int(sshuttle_pid)
|
sshuttle_pid = int(sshuttle_pid)
|
||||||
if user == '-':
|
if user == '-':
|
||||||
user = None
|
user = None
|
||||||
debug2('Got udp: %r, user: %r, tmark: %s, sshuttle_pid: %d' %
|
if group == '-':
|
||||||
(udp, user, tmark, sshuttle_pid))
|
group = None
|
||||||
|
debug2('Got udp: %r, user: %r, group: %r, tmark: %s, sshuttle_pid: %d' %
|
||||||
|
(udp, user, group, tmark, sshuttle_pid))
|
||||||
|
|
||||||
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
|
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
|
||||||
nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6]
|
nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6]
|
||||||
@ -280,14 +288,14 @@ def main(method_name, syslog):
|
|||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
port_v6, dnsport_v6, nslist_v6,
|
port_v6, dnsport_v6, nslist_v6,
|
||||||
socket.AF_INET6, subnets_v6, udp,
|
socket.AF_INET6, subnets_v6, udp,
|
||||||
user, tmark)
|
user, group, tmark)
|
||||||
|
|
||||||
if subnets_v4 or nslist_v4:
|
if subnets_v4 or nslist_v4:
|
||||||
debug2('setting up IPv4.')
|
debug2('setting up IPv4.')
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
port_v4, dnsport_v4, nslist_v4,
|
port_v4, dnsport_v4, nslist_v4,
|
||||||
socket.AF_INET, subnets_v4, udp,
|
socket.AF_INET, subnets_v4, udp,
|
||||||
user, tmark)
|
user, group, tmark)
|
||||||
|
|
||||||
flush_systemd_dns_cache()
|
flush_systemd_dns_cache()
|
||||||
stdout.write('STARTED\n')
|
stdout.write('STARTED\n')
|
||||||
@ -317,46 +325,46 @@ def main(method_name, syslog):
|
|||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
debug1('undoing changes.')
|
debug1('undoing changes.')
|
||||||
except BaseException:
|
except Exception:
|
||||||
debug2('An error occurred, ignoring it.')
|
debug2('An error occurred, ignoring it.')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if subnets_v6 or nslist_v6:
|
if subnets_v6 or nslist_v6:
|
||||||
debug2('undoing IPv6 changes.')
|
debug2('undoing IPv6 changes.')
|
||||||
method.restore_firewall(port_v6, socket.AF_INET6, udp, user)
|
method.restore_firewall(port_v6, socket.AF_INET6, udp, user, group)
|
||||||
except BaseException:
|
except Exception:
|
||||||
try:
|
try:
|
||||||
debug1("Error trying to undo IPv6 firewall.")
|
debug1("Error trying to undo IPv6 firewall.")
|
||||||
debug1(traceback.format_exc())
|
debug1(traceback.format_exc())
|
||||||
except BaseException:
|
except Exception:
|
||||||
debug2('An error occurred, ignoring it.')
|
debug2('An error occurred, ignoring it.')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if subnets_v4 or nslist_v4:
|
if subnets_v4 or nslist_v4:
|
||||||
debug2('undoing IPv4 changes.')
|
debug2('undoing IPv4 changes.')
|
||||||
method.restore_firewall(port_v4, socket.AF_INET, udp, user)
|
method.restore_firewall(port_v4, socket.AF_INET, udp, user, group)
|
||||||
except BaseException:
|
except Exception:
|
||||||
try:
|
try:
|
||||||
debug1("Error trying to undo IPv4 firewall.")
|
debug1("Error trying to undo IPv4 firewall.")
|
||||||
debug1(traceback.format_exc())
|
debug1(traceback.format_exc())
|
||||||
except BaseException:
|
except Exception:
|
||||||
debug2('An error occurred, ignoring it.')
|
debug2('An error occurred, ignoring it.')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# debug2() message printed in restore_etc_hosts() function.
|
# debug2() message printed in restore_etc_hosts() function.
|
||||||
restore_etc_hosts(hostmap, port_v6 or port_v4)
|
restore_etc_hosts(hostmap, port_v6 or port_v4)
|
||||||
except BaseException:
|
except Exception:
|
||||||
try:
|
try:
|
||||||
debug1("Error trying to undo /etc/hosts changes.")
|
debug1("Error trying to undo /etc/hosts changes.")
|
||||||
debug1(traceback.format_exc())
|
debug1(traceback.format_exc())
|
||||||
except BaseException:
|
except Exception:
|
||||||
debug2('An error occurred, ignoring it.')
|
debug2('An error occurred, ignoring it.')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
flush_systemd_dns_cache()
|
flush_systemd_dns_cache()
|
||||||
except BaseException:
|
except Exception:
|
||||||
try:
|
try:
|
||||||
debug1("Error trying to flush systemd dns cache.")
|
debug1("Error trying to flush systemd dns cache.")
|
||||||
debug1(traceback.format_exc())
|
debug1(traceback.format_exc())
|
||||||
except BaseException:
|
except Exception:
|
||||||
debug2("An error occurred, ignoring it.")
|
debug2("An error occurred, ignoring it.")
|
||||||
|
@ -22,14 +22,7 @@ def log(s):
|
|||||||
prefix = logprefix
|
prefix = logprefix
|
||||||
s = s.rstrip("\n")
|
s = s.rstrip("\n")
|
||||||
for line in s.split("\n"):
|
for line in s.split("\n"):
|
||||||
# We output with \r\n instead of \n because when we use
|
sys.stderr.write(prefix + line + "\n")
|
||||||
# sudo with the use_pty option, the firewall process, the
|
|
||||||
# other processes printing to the terminal will have the
|
|
||||||
# \n move to the next line, but they will fail to reset
|
|
||||||
# cursor to the beginning of the line. Printing output
|
|
||||||
# with \r\n endings fixes that problem and does not appear
|
|
||||||
# to cause problems elsewhere.
|
|
||||||
sys.stderr.write(prefix + line + "\r\n")
|
|
||||||
prefix = " "
|
prefix = " "
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
except IOError:
|
except IOError:
|
||||||
|
@ -55,7 +55,7 @@ def write_host_cache():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
os.unlink(tmpname)
|
os.unlink(tmpname)
|
||||||
except BaseException:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,8 +7,6 @@ from sshuttle.helpers import Fatal, debug3
|
|||||||
|
|
||||||
|
|
||||||
def original_dst(sock):
|
def original_dst(sock):
|
||||||
ip = "0.0.0.0"
|
|
||||||
port = -1
|
|
||||||
try:
|
try:
|
||||||
family = sock.family
|
family = sock.family
|
||||||
SO_ORIGINAL_DST = 80
|
SO_ORIGINAL_DST = 80
|
||||||
@ -52,6 +50,7 @@ class BaseMethod(object):
|
|||||||
result.udp = False
|
result.udp = False
|
||||||
result.dns = True
|
result.dns = True
|
||||||
result.user = False
|
result.user = False
|
||||||
|
result.group = False
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -72,7 +71,7 @@ class BaseMethod(object):
|
|||||||
|
|
||||||
def send_udp(self, sock, srcip, dstip, data):
|
def send_udp(self, sock, srcip, dstip, data):
|
||||||
if srcip is not None:
|
if srcip is not None:
|
||||||
Fatal("Method %s send_udp does not support setting srcip to %r"
|
raise Fatal("Method %s send_udp does not support setting srcip to %r"
|
||||||
% (self.name, srcip))
|
% (self.name, srcip))
|
||||||
sock.sendto(data, dstip)
|
sock.sendto(data, dstip)
|
||||||
|
|
||||||
@ -91,10 +90,10 @@ class BaseMethod(object):
|
|||||||
(key, self.name))
|
(key, self.name))
|
||||||
|
|
||||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||||
user, tmark):
|
user, group, tmark):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def restore_firewall(self, port, family, udp, user):
|
def restore_firewall(self, port, family, udp, user, group):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -52,7 +52,7 @@ def _fill_oldctls(prefix):
|
|||||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=get_env())
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=get_env())
|
||||||
for line in p.stdout:
|
for line in p.stdout:
|
||||||
line = line.decode()
|
line = line.decode()
|
||||||
assert(line[-1] == '\n')
|
assert line[-1] == '\n'
|
||||||
(k, v) = line[:-1].split(': ', 1)
|
(k, v) = line[:-1].split(': ', 1)
|
||||||
_oldctls[k] = v.strip()
|
_oldctls[k] = v.strip()
|
||||||
rv = p.wait()
|
rv = p.wait()
|
||||||
@ -74,7 +74,7 @@ _changedctls = []
|
|||||||
|
|
||||||
def sysctl_set(name, val, permanent=False):
|
def sysctl_set(name, val, permanent=False):
|
||||||
PREFIX = 'net.inet.ip'
|
PREFIX = 'net.inet.ip'
|
||||||
assert(name.startswith(PREFIX + '.'))
|
assert name.startswith(PREFIX + '.')
|
||||||
val = str(val)
|
val = str(val)
|
||||||
if not _oldctls:
|
if not _oldctls:
|
||||||
_fill_oldctls(PREFIX)
|
_fill_oldctls(PREFIX)
|
||||||
@ -156,7 +156,7 @@ class Method(BaseMethod):
|
|||||||
# udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVDSTADDR, 1)
|
# udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVDSTADDR, 1)
|
||||||
|
|
||||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||||
user, tmark):
|
user, group, tmark):
|
||||||
# IPv6 not supported
|
# IPv6 not supported
|
||||||
if family not in [socket.AF_INET]:
|
if family not in [socket.AF_INET]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@ -207,7 +207,7 @@ class Method(BaseMethod):
|
|||||||
else:
|
else:
|
||||||
ipfw('table', '126', 'add', '%s/%s' % (snet, swidth))
|
ipfw('table', '126', 'add', '%s/%s' % (snet, swidth))
|
||||||
|
|
||||||
def restore_firewall(self, port, family, udp, user):
|
def restore_firewall(self, port, family, udp, user, group):
|
||||||
if family not in [socket.AF_INET]:
|
if family not in [socket.AF_INET]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by ipfw method'
|
'Address family "%s" unsupported by ipfw method'
|
||||||
|
@ -13,7 +13,7 @@ class Method(BaseMethod):
|
|||||||
# recently-started one will win (because we use "-I OUTPUT 1" instead of
|
# recently-started one will win (because we use "-I OUTPUT 1" instead of
|
||||||
# "-A OUTPUT").
|
# "-A OUTPUT").
|
||||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||||
user, tmark):
|
user, group, tmark):
|
||||||
if family != socket.AF_INET and family != socket.AF_INET6:
|
if family != socket.AF_INET and family != socket.AF_INET6:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by nat method_name'
|
'Address family "%s" unsupported by nat method_name'
|
||||||
@ -31,13 +31,18 @@ class Method(BaseMethod):
|
|||||||
chain = 'sshuttle-%s' % port
|
chain = 'sshuttle-%s' % port
|
||||||
|
|
||||||
# basic cleanup/setup of chains
|
# basic cleanup/setup of chains
|
||||||
self.restore_firewall(port, family, udp, user)
|
self.restore_firewall(port, family, udp, user, group)
|
||||||
|
|
||||||
_ipt('-N', chain)
|
_ipt('-N', chain)
|
||||||
_ipt('-F', chain)
|
_ipt('-F', chain)
|
||||||
|
if user is not None or group is not None:
|
||||||
|
margs = ['-I', 'OUTPUT', '1', '-m', 'owner']
|
||||||
if user is not None:
|
if user is not None:
|
||||||
_ipm('-I', 'OUTPUT', '1', '-m', 'owner', '--uid-owner', str(user),
|
margs += ['--uid-owner', str(user)]
|
||||||
'-j', 'MARK', '--set-mark', str(port))
|
if group is not None:
|
||||||
|
margs += ['--gid-owner', str(group)]
|
||||||
|
margs += ['-j', 'MARK', '--set-mark', str(port)]
|
||||||
|
nonfatal(_ipm, *margs)
|
||||||
args = '-m', 'mark', '--mark', str(port), '-j', chain
|
args = '-m', 'mark', '--mark', str(port), '-j', chain
|
||||||
else:
|
else:
|
||||||
args = '-j', chain
|
args = '-j', chain
|
||||||
@ -54,11 +59,6 @@ class Method(BaseMethod):
|
|||||||
'--dport', '53',
|
'--dport', '53',
|
||||||
'--to-ports', str(dnsport))
|
'--to-ports', str(dnsport))
|
||||||
|
|
||||||
# Don't route any remaining local traffic through sshuttle.
|
|
||||||
_ipt('-A', chain, '-j', 'RETURN',
|
|
||||||
'-m', 'addrtype',
|
|
||||||
'--dst-type', 'LOCAL')
|
|
||||||
|
|
||||||
# create new subnet entries.
|
# create new subnet entries.
|
||||||
for _, swidth, sexclude, snet, fport, lport \
|
for _, swidth, sexclude, snet, fport, lport \
|
||||||
in sorted(subnets, key=subnet_weight, reverse=True):
|
in sorted(subnets, key=subnet_weight, reverse=True):
|
||||||
@ -75,7 +75,12 @@ class Method(BaseMethod):
|
|||||||
'--dest', '%s/%s' % (snet, swidth),
|
'--dest', '%s/%s' % (snet, swidth),
|
||||||
*(tcp_ports + ('--to-ports', str(port))))
|
*(tcp_ports + ('--to-ports', str(port))))
|
||||||
|
|
||||||
def restore_firewall(self, port, family, udp, user):
|
# Don't route any remaining local traffic through sshuttle.
|
||||||
|
_ipt('-A', chain, '-j', 'RETURN',
|
||||||
|
'-m', 'addrtype',
|
||||||
|
'--dst-type', 'LOCAL')
|
||||||
|
|
||||||
|
def restore_firewall(self, port, family, udp, user, group):
|
||||||
# only ipv4 supported with NAT
|
# only ipv4 supported with NAT
|
||||||
if family != socket.AF_INET and family != socket.AF_INET6:
|
if family != socket.AF_INET and family != socket.AF_INET6:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@ -96,9 +101,15 @@ class Method(BaseMethod):
|
|||||||
|
|
||||||
# basic cleanup/setup of chains
|
# basic cleanup/setup of chains
|
||||||
if ipt_chain_exists(family, table, chain):
|
if ipt_chain_exists(family, table, chain):
|
||||||
|
if user is not None or group is not None:
|
||||||
|
margs = ['-D', 'OUTPUT', '-m', 'owner']
|
||||||
if user is not None:
|
if user is not None:
|
||||||
nonfatal(_ipm, '-D', 'OUTPUT', '-m', 'owner', '--uid-owner',
|
margs += ['--uid-owner', str(user)]
|
||||||
str(user), '-j', 'MARK', '--set-mark', str(port))
|
if group is not None:
|
||||||
|
margs += ['--gid-owner', str(group)]
|
||||||
|
margs += ['-j', 'MARK', '--set-mark', str(port)]
|
||||||
|
nonfatal(_ipm, *margs)
|
||||||
|
|
||||||
args = '-m', 'mark', '--mark', str(port), '-j', chain
|
args = '-m', 'mark', '--mark', str(port), '-j', chain
|
||||||
else:
|
else:
|
||||||
args = '-j', chain
|
args = '-j', chain
|
||||||
@ -111,6 +122,7 @@ class Method(BaseMethod):
|
|||||||
result = super(Method, self).get_supported_features()
|
result = super(Method, self).get_supported_features()
|
||||||
result.user = True
|
result.user = True
|
||||||
result.ipv6 = True
|
result.ipv6 = True
|
||||||
|
result.group = True
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def is_supported(self):
|
def is_supported(self):
|
||||||
|
@ -13,7 +13,7 @@ class Method(BaseMethod):
|
|||||||
# recently-started one will win (because we use "-I OUTPUT 1" instead of
|
# recently-started one will win (because we use "-I OUTPUT 1" instead of
|
||||||
# "-A OUTPUT").
|
# "-A OUTPUT").
|
||||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||||
user, tmark):
|
user, group, tmark):
|
||||||
if udp:
|
if udp:
|
||||||
raise Exception("UDP not supported by nft")
|
raise Exception("UDP not supported by nft")
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ class Method(BaseMethod):
|
|||||||
ip_version, 'daddr %s/%s' % (snet, swidth),
|
ip_version, 'daddr %s/%s' % (snet, swidth),
|
||||||
('redirect to :' + str(port)))))
|
('redirect to :' + str(port)))))
|
||||||
|
|
||||||
def restore_firewall(self, port, family, udp, user):
|
def restore_firewall(self, port, family, udp, user, group):
|
||||||
if udp:
|
if udp:
|
||||||
raise Exception("UDP not supported by nft method_name")
|
raise Exception("UDP not supported by nft method_name")
|
||||||
|
|
||||||
|
@ -448,7 +448,7 @@ class Method(BaseMethod):
|
|||||||
return sock.getsockname()
|
return sock.getsockname()
|
||||||
|
|
||||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||||
user, tmark):
|
user, group, tmark):
|
||||||
if family not in [socket.AF_INET, socket.AF_INET6]:
|
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by pf method_name'
|
'Address family "%s" unsupported by pf method_name'
|
||||||
@ -473,7 +473,7 @@ class Method(BaseMethod):
|
|||||||
pf.add_rules(anchor, includes, port, dnsport, nslist, family)
|
pf.add_rules(anchor, includes, port, dnsport, nslist, family)
|
||||||
pf.enable()
|
pf.enable()
|
||||||
|
|
||||||
def restore_firewall(self, port, family, udp, user):
|
def restore_firewall(self, port, family, udp, user, group):
|
||||||
if family not in [socket.AF_INET, socket.AF_INET6]:
|
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by pf method_name'
|
'Address family "%s" unsupported by pf method_name'
|
||||||
|
@ -114,7 +114,7 @@ class Method(BaseMethod):
|
|||||||
udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
|
udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
|
||||||
|
|
||||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||||
user, tmark):
|
user, group, tmark):
|
||||||
if family not in [socket.AF_INET, socket.AF_INET6]:
|
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by tproxy method'
|
'Address family "%s" unsupported by tproxy method'
|
||||||
@ -134,7 +134,7 @@ class Method(BaseMethod):
|
|||||||
divert_chain = 'sshuttle-d-%s' % port
|
divert_chain = 'sshuttle-d-%s' % port
|
||||||
|
|
||||||
# basic cleanup/setup of chains
|
# basic cleanup/setup of chains
|
||||||
self.restore_firewall(port, family, udp, user)
|
self.restore_firewall(port, family, udp, user, group)
|
||||||
|
|
||||||
_ipt('-N', mark_chain)
|
_ipt('-N', mark_chain)
|
||||||
_ipt('-F', mark_chain)
|
_ipt('-F', mark_chain)
|
||||||
@ -145,8 +145,18 @@ class Method(BaseMethod):
|
|||||||
_ipt('-I', 'OUTPUT', '1', '-j', mark_chain)
|
_ipt('-I', 'OUTPUT', '1', '-j', mark_chain)
|
||||||
_ipt('-I', 'PREROUTING', '1', '-j', tproxy_chain)
|
_ipt('-I', 'PREROUTING', '1', '-j', tproxy_chain)
|
||||||
|
|
||||||
|
for _, ip in [i for i in nslist if i[0] == family]:
|
||||||
|
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', tmark,
|
||||||
|
'--dest', '%s/32' % ip,
|
||||||
|
'-m', 'udp', '-p', 'udp', '--dport', '53')
|
||||||
|
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
||||||
|
'--tproxy-mark', tmark,
|
||||||
|
'--dest', '%s/32' % ip,
|
||||||
|
'-m', 'udp', '-p', 'udp', '--dport', '53',
|
||||||
|
'--on-port', str(dnsport))
|
||||||
|
|
||||||
# Don't have packets sent to any of our local IP addresses go
|
# Don't have packets sent to any of our local IP addresses go
|
||||||
# through the tproxy or mark chains.
|
# through the tproxy or mark chains (except DNS ones).
|
||||||
#
|
#
|
||||||
# Without this fix, if a large subnet is redirected through
|
# Without this fix, if a large subnet is redirected through
|
||||||
# sshuttle (i.e., 0/0), then the user may be unable to receive
|
# sshuttle (i.e., 0/0), then the user may be unable to receive
|
||||||
@ -169,16 +179,6 @@ class Method(BaseMethod):
|
|||||||
_ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain,
|
_ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain,
|
||||||
'-m', 'udp', '-p', 'udp')
|
'-m', 'udp', '-p', 'udp')
|
||||||
|
|
||||||
for _, ip in [i for i in nslist if i[0] == family]:
|
|
||||||
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', tmark,
|
|
||||||
'--dest', '%s/32' % ip,
|
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '53')
|
|
||||||
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
|
||||||
'--tproxy-mark', tmark,
|
|
||||||
'--dest', '%s/32' % ip,
|
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '53',
|
|
||||||
'--on-port', str(dnsport))
|
|
||||||
|
|
||||||
for _, swidth, sexclude, snet, fport, lport \
|
for _, swidth, sexclude, snet, fport, lport \
|
||||||
in sorted(subnets, key=subnet_weight, reverse=True):
|
in sorted(subnets, key=subnet_weight, reverse=True):
|
||||||
tcp_ports = ('-p', 'tcp')
|
tcp_ports = ('-p', 'tcp')
|
||||||
@ -228,7 +228,7 @@ class Method(BaseMethod):
|
|||||||
'-m', 'udp',
|
'-m', 'udp',
|
||||||
*(udp_ports + ('--on-port', str(port))))
|
*(udp_ports + ('--on-port', str(port))))
|
||||||
|
|
||||||
def restore_firewall(self, port, family, udp, user):
|
def restore_firewall(self, port, family, udp, user, group):
|
||||||
if family not in [socket.AF_INET, socket.AF_INET6]:
|
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by tproxy method'
|
'Address family "%s" unsupported by tproxy method'
|
||||||
|
@ -37,9 +37,9 @@ def parse_subnetport_file(s):
|
|||||||
def parse_subnetport(s):
|
def parse_subnetport(s):
|
||||||
|
|
||||||
if s.count(':') > 1:
|
if s.count(':') > 1:
|
||||||
rx = r'(?:\[?([\w\:]+)(?:/(\d+))?]?)(?::(\d+)(?:-(\d+))?)?$'
|
rx = r'(?:\[?(?:\*\.)?([\w\:]+)(?:/(\d+))?]?)(?::(\d+)(?:-(\d+))?)?$'
|
||||||
else:
|
else:
|
||||||
rx = r'([\w\.\-]+)(?:/(\d+))?(?::(\d+)(?:-(\d+))?)?$'
|
rx = r'((?:\*\.)?[\w\.\-]+)(?:/(\d+))?(?::(\d+)(?:-(\d+))?)?$'
|
||||||
|
|
||||||
m = re.match(rx, s)
|
m = re.match(rx, s)
|
||||||
if not m:
|
if not m:
|
||||||
@ -382,6 +382,12 @@ parser.add_argument(
|
|||||||
apply all the rules only to this linux user
|
apply all the rules only to this linux user
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--group",
|
||||||
|
help="""
|
||||||
|
apply all the rules only to this linux group
|
||||||
|
"""
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--firewall",
|
"--firewall",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@ -396,18 +402,15 @@ parser.add_argument(
|
|||||||
(internal use only)
|
(internal use only)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--sudoers",
|
|
||||||
action="store_true",
|
|
||||||
help="""
|
|
||||||
Add sshuttle to the sudoers for this user
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--sudoers-no-modify",
|
"--sudoers-no-modify",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="""
|
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(
|
parser.add_argument(
|
||||||
@ -415,16 +418,7 @@ parser.add_argument(
|
|||||||
default="",
|
default="",
|
||||||
help="""
|
help="""
|
||||||
Set the user name or group with %%group_name for passwordless operation.
|
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
|
Default is the current user. Only works with the --sudoers-no-modify option.
|
||||||
--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.
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
@ -34,7 +34,6 @@ def _ipmatch(ipstr):
|
|||||||
elif g[3] is None:
|
elif g[3] is None:
|
||||||
ips += '.0'
|
ips += '.0'
|
||||||
width = min(width, 24)
|
width = min(width, 24)
|
||||||
ips = ips
|
|
||||||
return (struct.unpack('!I', socket.inet_aton(ips))[0], width)
|
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('')
|
hw.leftover = b('')
|
||||||
|
|
||||||
def hostwatch_ready(sock):
|
def hostwatch_ready(sock):
|
||||||
assert(hw.pid)
|
assert hw.pid
|
||||||
content = hw.sock.recv(4096)
|
content = hw.sock.recv(4096)
|
||||||
if content:
|
if content:
|
||||||
lines = (hw.leftover + content).split(b('\n'))
|
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:
|
while mux.ok:
|
||||||
if hw.pid:
|
if hw.pid:
|
||||||
assert(hw.pid > 0)
|
assert hw.pid > 0
|
||||||
(rpid, rv) = os.waitpid(hw.pid, os.WNOHANG)
|
(rpid, rv) = os.waitpid(hw.pid, os.WNOHANG)
|
||||||
if rpid:
|
if rpid:
|
||||||
raise Fatal(
|
raise Fatal(
|
||||||
|
@ -193,7 +193,6 @@ class SockWrapper:
|
|||||||
if not self.shut_read:
|
if not self.shut_read:
|
||||||
debug2('%r: done reading' % self)
|
debug2('%r: done reading' % self)
|
||||||
self.shut_read = True
|
self.shut_read = True
|
||||||
# self.rsock.shutdown(SHUT_RD) # doesn't do anything anyway
|
|
||||||
|
|
||||||
def nowrite(self):
|
def nowrite(self):
|
||||||
if not self.shut_write:
|
if not self.shut_write:
|
||||||
@ -227,7 +226,7 @@ class SockWrapper:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
def write(self, buf):
|
def write(self, buf):
|
||||||
assert(buf)
|
assert buf
|
||||||
return self.uwrite(buf)
|
return self.uwrite(buf)
|
||||||
|
|
||||||
def uread(self):
|
def uread(self):
|
||||||
@ -373,11 +372,6 @@ class Mux(Handler):
|
|||||||
if not self.too_full:
|
if not self.too_full:
|
||||||
self.send(0, CMD_PING, b('rttest'))
|
self.send(0, CMD_PING, b('rttest'))
|
||||||
self.too_full = True
|
self.too_full = True
|
||||||
# ob = []
|
|
||||||
# for b in self.outbuf:
|
|
||||||
# (s1,s2,c) = struct.unpack('!ccH', b[:4])
|
|
||||||
# ob.append(c)
|
|
||||||
# log('outbuf: %d %r' % (self.amount_queued(), ob))
|
|
||||||
|
|
||||||
def send(self, channel, cmd, data):
|
def send(self, channel, cmd, data):
|
||||||
assert isinstance(data, bytes)
|
assert isinstance(data, bytes)
|
||||||
@ -402,15 +396,15 @@ class Mux(Handler):
|
|||||||
elif cmd == CMD_EXIT:
|
elif cmd == CMD_EXIT:
|
||||||
self.ok = False
|
self.ok = False
|
||||||
elif cmd == CMD_TCP_CONNECT:
|
elif cmd == CMD_TCP_CONNECT:
|
||||||
assert(not self.channels.get(channel))
|
assert not self.channels.get(channel)
|
||||||
if self.new_channel:
|
if self.new_channel:
|
||||||
self.new_channel(channel, data)
|
self.new_channel(channel, data)
|
||||||
elif cmd == CMD_DNS_REQ:
|
elif cmd == CMD_DNS_REQ:
|
||||||
assert(not self.channels.get(channel))
|
assert not self.channels.get(channel)
|
||||||
if self.got_dns_req:
|
if self.got_dns_req:
|
||||||
self.got_dns_req(channel, data)
|
self.got_dns_req(channel, data)
|
||||||
elif cmd == CMD_UDP_OPEN:
|
elif cmd == CMD_UDP_OPEN:
|
||||||
assert(not self.channels.get(channel))
|
assert not self.channels.get(channel)
|
||||||
if self.got_udp_open:
|
if self.got_udp_open:
|
||||||
self.got_udp_open(channel, data)
|
self.got_udp_open(channel, data)
|
||||||
elif cmd == CMD_ROUTES:
|
elif cmd == CMD_ROUTES:
|
||||||
@ -443,7 +437,7 @@ class Mux(Handler):
|
|||||||
# python < 3.5
|
# python < 3.5
|
||||||
flags = fcntl.fcntl(self.wfile.fileno(), fcntl.F_GETFL)
|
flags = fcntl.fcntl(self.wfile.fileno(), fcntl.F_GETFL)
|
||||||
flags |= os.O_NONBLOCK
|
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]:
|
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' % (wrote, len(self.outbuf[0])))
|
debug2('mux wrote: %r/%d' % (wrote, len(self.outbuf[0])))
|
||||||
@ -459,7 +453,7 @@ class Mux(Handler):
|
|||||||
# python < 3.5
|
# python < 3.5
|
||||||
flags = fcntl.fcntl(self.rfile.fileno(), fcntl.F_GETFL)
|
flags = fcntl.fcntl(self.rfile.fileno(), fcntl.F_GETFL)
|
||||||
flags |= os.O_NONBLOCK
|
flags |= os.O_NONBLOCK
|
||||||
flags = fcntl.fcntl(self.rfile.fileno(), fcntl.F_SETFL, flags)
|
fcntl.fcntl(self.rfile.fileno(), fcntl.F_SETFL, flags)
|
||||||
try:
|
try:
|
||||||
# If LATENCY_BUFFER_SIZE is inappropriately large, we will
|
# If LATENCY_BUFFER_SIZE is inappropriately large, we will
|
||||||
# get a MemoryError here. Read no more than 1MiB.
|
# get a MemoryError here. Read no more than 1MiB.
|
||||||
@ -476,14 +470,12 @@ class Mux(Handler):
|
|||||||
|
|
||||||
def handle(self):
|
def handle(self):
|
||||||
self.fill()
|
self.fill()
|
||||||
# log('inbuf is: (%d,%d) %r'
|
|
||||||
# % (self.want, len(self.inbuf), self.inbuf))
|
|
||||||
while 1:
|
while 1:
|
||||||
if len(self.inbuf) >= (self.want or HDR_LEN):
|
if len(self.inbuf) >= (self.want or HDR_LEN):
|
||||||
(s1, s2, channel, cmd, datalen) = \
|
(s1, s2, channel, cmd, datalen) = \
|
||||||
struct.unpack('!ccHHH', self.inbuf[:HDR_LEN])
|
struct.unpack('!ccHHH', self.inbuf[:HDR_LEN])
|
||||||
assert(s1 == b('S'))
|
assert s1 == b('S')
|
||||||
assert(s2 == b('S'))
|
assert s2 == b('S')
|
||||||
self.want = datalen + HDR_LEN
|
self.want = datalen + HDR_LEN
|
||||||
if self.want and len(self.inbuf) >= self.want:
|
if self.want and len(self.inbuf) >= self.want:
|
||||||
data = self.inbuf[HDR_LEN:self.want]
|
data = self.inbuf[HDR_LEN:self.want]
|
||||||
|
@ -10,7 +10,7 @@ def start_syslog():
|
|||||||
global _p
|
global _p
|
||||||
with open(os.devnull, 'w') as devnull:
|
with open(os.devnull, 'w') as devnull:
|
||||||
_p = ssubprocess.Popen(
|
_p = ssubprocess.Popen(
|
||||||
['logger', '-p', 'daemon.notice', '-t', 'sshuttle'],
|
['logger', '-p', 'daemon.err', '-t', 'sshuttle'],
|
||||||
stdin=ssubprocess.PIPE,
|
stdin=ssubprocess.PIPE,
|
||||||
stdout=devnull,
|
stdout=devnull,
|
||||||
stderr=devnull
|
stderr=devnull
|
||||||
|
@ -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 sys
|
||||||
import getpass
|
import getpass
|
||||||
from uuid import uuid4
|
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
|
def build_config(user_name):
|
||||||
command_alias = 'SSHUTTLE%(num)s' % {'num': uuid4().hex[-3:].upper()}
|
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 *
|
Cmnd_Alias %(ca)s = /usr/bin/env PYTHONPATH=%(dist_packages)s %(py)s %(path)s *
|
||||||
|
|
||||||
%(user_name)s ALL=NOPASSWD: %(ca)s
|
%(user_name)s ALL=NOPASSWD: %(ca)s
|
||||||
'''
|
'''
|
||||||
|
|
||||||
warning_msg = "# WARNING: When you allow a user to run sshuttle as root,\n" \
|
content = template % {
|
||||||
"# they can then use sshuttle's --ssh-cmd option to run any\n" \
|
# randomize command alias to avoid collisions
|
||||||
"# command as root.\n"
|
'ca': 'SSHUTTLE%(num)s' % {'num': uuid4().hex[-3:].upper()},
|
||||||
|
'dist_packages': os.path.dirname(os.path.abspath(__file__))[:-9],
|
||||||
|
|
||||||
def build_config(user_name):
|
|
||||||
content = warning_msg
|
|
||||||
content += template % {
|
|
||||||
'ca': command_alias,
|
|
||||||
'dist_packages': path_to_dist_packages,
|
|
||||||
'py': sys.executable,
|
'py': sys.executable,
|
||||||
'path': path_to_sshuttle,
|
'path': sys.argv[0],
|
||||||
'user_name': user_name,
|
'user_name': user_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
def save_config(content, file_name):
|
def sudoers(user_name=None):
|
||||||
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):
|
|
||||||
user_name = user_name or getpass.getuser()
|
user_name = user_name or getpass.getuser()
|
||||||
content = build_config(user_name)
|
content = build_config(user_name)
|
||||||
|
|
||||||
if no_modify:
|
|
||||||
sys.stdout.write(content)
|
sys.stdout.write(content)
|
||||||
exit(0)
|
exit(0)
|
||||||
else:
|
|
||||||
sys.stdout.write(warning_msg)
|
|
||||||
save_config(content, file_name)
|
|
||||||
|
1
sshuttle/version.py
Normal file
1
sshuttle/version.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
__version__ = version = '1.1.2'
|
@ -1,7 +1,11 @@
|
|||||||
import io
|
import io
|
||||||
|
import os
|
||||||
from socket import AF_INET, AF_INET6
|
from socket import AF_INET, AF_INET6
|
||||||
|
|
||||||
from unittest.mock import Mock, patch, call
|
from unittest.mock import Mock, patch, call
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
import sshuttle.firewall
|
import sshuttle.firewall
|
||||||
|
|
||||||
|
|
||||||
@ -15,7 +19,7 @@ NSLIST
|
|||||||
{inet},1.2.3.33
|
{inet},1.2.3.33
|
||||||
{inet6},2404:6800:4004:80c::33
|
{inet6},2404:6800:4004:80c::33
|
||||||
PORTS 1024,1025,1026,1027
|
PORTS 1024,1025,1026,1027
|
||||||
GO 1 - 0x01 12345
|
GO 1 - - 0x01 12345
|
||||||
HOST 1.2.3.3,existing
|
HOST 1.2.3.3,existing
|
||||||
""".format(inet=AF_INET, inet6=AF_INET6))
|
""".format(inet=AF_INET, inet6=AF_INET6))
|
||||||
stdout = Mock()
|
stdout = Mock()
|
||||||
@ -59,6 +63,21 @@ def test_rewrite_etc_hosts(tmpdir):
|
|||||||
assert orig_hosts.computehash() == new_hosts.computehash()
|
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():
|
def test_subnet_weight():
|
||||||
subnets = [
|
subnets = [
|
||||||
(AF_INET, 16, 0, '192.168.0.0', 0, 0),
|
(AF_INET, 16, 0, '192.168.0.0', 0, 0),
|
||||||
@ -126,6 +145,7 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
|
|||||||
(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,
|
True,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01'),
|
'0x01'),
|
||||||
call().setup_firewall(
|
call().setup_firewall(
|
||||||
1025, 1027,
|
1025, 1027,
|
||||||
@ -135,7 +155,8 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
|
|||||||
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||||
True,
|
True,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01'),
|
'0x01'),
|
||||||
call().restore_firewall(1024, AF_INET6, True, None),
|
call().restore_firewall(1024, AF_INET6, True, None, None),
|
||||||
call().restore_firewall(1025, AF_INET, True, None),
|
call().restore_firewall(1025, AF_INET, True, None, None),
|
||||||
]
|
]
|
||||||
|
@ -24,19 +24,19 @@ def test_log(mock_stderr, mock_stdout):
|
|||||||
call.flush(),
|
call.flush(),
|
||||||
]
|
]
|
||||||
assert mock_stderr.mock_calls == [
|
assert mock_stderr.mock_calls == [
|
||||||
call.write('prefix: message\r\n'),
|
call.write('prefix: message\n'),
|
||||||
call.flush(),
|
call.flush(),
|
||||||
call.write('prefix: abc\r\n'),
|
call.write('prefix: abc\n'),
|
||||||
call.flush(),
|
call.flush(),
|
||||||
call.write('prefix: message 1\r\n'),
|
call.write('prefix: message 1\n'),
|
||||||
call.flush(),
|
call.flush(),
|
||||||
call.write('prefix: message 2\r\n'),
|
call.write('prefix: message 2\n'),
|
||||||
call.write(' line2\r\n'),
|
call.write(' line2\n'),
|
||||||
call.write(' line3\r\n'),
|
call.write(' line3\n'),
|
||||||
call.flush(),
|
call.flush(),
|
||||||
call.write('prefix: message 3\r\n'),
|
call.write('prefix: message 3\n'),
|
||||||
call.write(' line2\r\n'),
|
call.write(' line2\n'),
|
||||||
call.write(' line3\r\n'),
|
call.write(' line3\n'),
|
||||||
call.flush(),
|
call.flush(),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ def test_debug1(mock_stderr, mock_stdout):
|
|||||||
call.flush(),
|
call.flush(),
|
||||||
]
|
]
|
||||||
assert mock_stderr.mock_calls == [
|
assert mock_stderr.mock_calls == [
|
||||||
call.write('prefix: message\r\n'),
|
call.write('prefix: message\n'),
|
||||||
call.flush(),
|
call.flush(),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ def test_debug2(mock_stderr, mock_stdout):
|
|||||||
call.flush(),
|
call.flush(),
|
||||||
]
|
]
|
||||||
assert mock_stderr.mock_calls == [
|
assert mock_stderr.mock_calls == [
|
||||||
call.write('prefix: message\r\n'),
|
call.write('prefix: message\n'),
|
||||||
call.flush(),
|
call.flush(),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ def test_debug3(mock_stderr, mock_stdout):
|
|||||||
call.flush(),
|
call.flush(),
|
||||||
]
|
]
|
||||||
assert mock_stderr.mock_calls == [
|
assert mock_stderr.mock_calls == [
|
||||||
call.write('prefix: message\r\n'),
|
call.write('prefix: message\n'),
|
||||||
call.flush(),
|
call.flush(),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -192,5 +192,4 @@ def test_family_ip_tuple():
|
|||||||
def test_family_to_string():
|
def test_family_to_string():
|
||||||
assert sshuttle.helpers.family_to_string(AF_INET) == "AF_INET"
|
assert sshuttle.helpers.family_to_string(AF_INET) == "AF_INET"
|
||||||
assert sshuttle.helpers.family_to_string(AF_INET6) == "AF_INET6"
|
assert sshuttle.helpers.family_to_string(AF_INET6) == "AF_INET6"
|
||||||
expected = 'AddressFamily.AF_UNIX'
|
assert isinstance(sshuttle.helpers.family_to_string(socket.AF_UNIX), str)
|
||||||
assert sshuttle.helpers.family_to_string(socket.AF_UNIX) == expected
|
|
||||||
|
@ -81,7 +81,7 @@ def test_assert_features():
|
|||||||
|
|
||||||
def test_firewall_command():
|
def test_firewall_command():
|
||||||
method = get_method('nat')
|
method = get_method('nat')
|
||||||
assert not method.firewall_command("somthing")
|
assert not method.firewall_command("something")
|
||||||
|
|
||||||
|
|
||||||
@patch('sshuttle.methods.nat.ipt')
|
@patch('sshuttle.methods.nat.ipt')
|
||||||
@ -101,6 +101,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
|
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
|
||||||
False,
|
False,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
|
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
@ -118,14 +119,14 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'REDIRECT',
|
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'REDIRECT',
|
||||||
'--dest', u'2404:6800:4004:80c::33', '-p', 'udp',
|
'--dest', u'2404:6800:4004:80c::33', '-p', 'udp',
|
||||||
'--dport', '53', '--to-ports', '1026'),
|
'--dport', '53', '--to-ports', '1026'),
|
||||||
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'RETURN',
|
|
||||||
'-m', 'addrtype', '--dst-type', 'LOCAL'),
|
|
||||||
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'RETURN',
|
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'RETURN',
|
||||||
'--dest', u'2404:6800:4004:80c::101f/128', '-p', 'tcp',
|
'--dest', u'2404:6800:4004:80c::101f/128', '-p', 'tcp',
|
||||||
'--dport', '80:80'),
|
'--dport', '80:80'),
|
||||||
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'REDIRECT',
|
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'REDIRECT',
|
||||||
'--dest', u'2404:6800:4004:80c::/64', '-p', 'tcp',
|
'--dest', u'2404:6800:4004:80c::/64', '-p', 'tcp',
|
||||||
'--to-ports', '1024')
|
'--to-ports', '1024'),
|
||||||
|
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'RETURN',
|
||||||
|
'-m', 'addrtype', '--dst-type', 'LOCAL')
|
||||||
]
|
]
|
||||||
mock_ipt_chain_exists.reset_mock()
|
mock_ipt_chain_exists.reset_mock()
|
||||||
mock_ipt.reset_mock()
|
mock_ipt.reset_mock()
|
||||||
@ -142,6 +143,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||||
True,
|
True,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
assert str(excinfo.value) == 'UDP not supported by nat method_name'
|
assert str(excinfo.value) == 'UDP not supported by nat method_name'
|
||||||
assert mock_ipt_chain_exists.mock_calls == []
|
assert mock_ipt_chain_exists.mock_calls == []
|
||||||
@ -155,6 +157,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||||
False,
|
False,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(AF_INET, 'nat', 'sshuttle-1025')
|
call(AF_INET, 'nat', 'sshuttle-1025')
|
||||||
@ -171,18 +174,18 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
|
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
|
||||||
'--dest', u'1.2.3.33', '-p', 'udp',
|
'--dest', u'1.2.3.33', '-p', 'udp',
|
||||||
'--dport', '53', '--to-ports', '1027'),
|
'--dport', '53', '--to-ports', '1027'),
|
||||||
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
|
|
||||||
'-m', 'addrtype', '--dst-type', 'LOCAL'),
|
|
||||||
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
|
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
|
||||||
'--dest', u'1.2.3.66/32', '-p', 'tcp', '--dport', '8080:8080'),
|
'--dest', u'1.2.3.66/32', '-p', 'tcp', '--dport', '8080:8080'),
|
||||||
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
|
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
|
||||||
'--dest', u'1.2.3.0/24', '-p', 'tcp', '--dport', '8000:9000',
|
'--dest', u'1.2.3.0/24', '-p', 'tcp', '--dport', '8000:9000',
|
||||||
'--to-ports', '1025')
|
'--to-ports', '1025'),
|
||||||
|
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
|
||||||
|
'-m', 'addrtype', '--dst-type', 'LOCAL'),
|
||||||
]
|
]
|
||||||
mock_ipt_chain_exists.reset_mock()
|
mock_ipt_chain_exists.reset_mock()
|
||||||
mock_ipt.reset_mock()
|
mock_ipt.reset_mock()
|
||||||
|
|
||||||
method.restore_firewall(1025, AF_INET, False, None)
|
method.restore_firewall(1025, AF_INET, False, None, None)
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(AF_INET, 'nat', 'sshuttle-1025')
|
call(AF_INET, 'nat', 'sshuttle-1025')
|
||||||
]
|
]
|
||||||
@ -197,7 +200,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
mock_ipt_chain_exists.reset_mock()
|
mock_ipt_chain_exists.reset_mock()
|
||||||
mock_ipt.reset_mock()
|
mock_ipt.reset_mock()
|
||||||
|
|
||||||
method.restore_firewall(1025, AF_INET6, False, None)
|
method.restore_firewall(1025, AF_INET6, False, None, None)
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(AF_INET6, 'nat', 'sshuttle-1025')
|
call(AF_INET6, 'nat', 'sshuttle-1025')
|
||||||
]
|
]
|
||||||
|
@ -92,7 +92,7 @@ def test_assert_features():
|
|||||||
@patch('sshuttle.methods.pf.pf_get_dev')
|
@patch('sshuttle.methods.pf.pf_get_dev')
|
||||||
def test_firewall_command_darwin(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
def test_firewall_command_darwin(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||||
method = get_method('pf')
|
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" % (
|
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
||||||
AF_INET, socket.IPPROTO_TCP,
|
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')
|
@patch('sshuttle.methods.pf.pf_get_dev')
|
||||||
def test_firewall_command_freebsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
def test_firewall_command_freebsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||||
method = get_method('pf')
|
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" % (
|
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
||||||
AF_INET, socket.IPPROTO_TCP,
|
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')
|
@patch('sshuttle.methods.pf.pf_get_dev')
|
||||||
def test_firewall_command_openbsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
def test_firewall_command_openbsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||||
method = get_method('pf')
|
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" % (
|
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
||||||
AF_INET, socket.IPPROTO_TCP,
|
AF_INET, socket.IPPROTO_TCP,
|
||||||
@ -187,6 +187,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||||
False,
|
False,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||||
@ -227,6 +228,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
True,
|
True,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||||
assert mock_pf_get_dev.mock_calls == []
|
assert mock_pf_get_dev.mock_calls == []
|
||||||
@ -241,6 +243,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
False,
|
False,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||||
@ -270,7 +273,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
mock_ioctl.reset_mock()
|
mock_ioctl.reset_mock()
|
||||||
mock_pfctl.reset_mock()
|
mock_pfctl.reset_mock()
|
||||||
|
|
||||||
method.restore_firewall(1025, AF_INET, False, None)
|
method.restore_firewall(1025, AF_INET, False, None, None)
|
||||||
assert mock_ioctl.mock_calls == []
|
assert mock_ioctl.mock_calls == []
|
||||||
assert mock_pfctl.mock_calls == [
|
assert mock_pfctl.mock_calls == [
|
||||||
call('-a sshuttle-1025 -F all'),
|
call('-a sshuttle-1025 -F all'),
|
||||||
@ -302,6 +305,7 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl,
|
|||||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||||
False,
|
False,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
|
|
||||||
assert mock_pfctl.mock_calls == [
|
assert mock_pfctl.mock_calls == [
|
||||||
@ -335,6 +339,7 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl,
|
|||||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
True,
|
True,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||||
assert mock_pf_get_dev.mock_calls == []
|
assert mock_pf_get_dev.mock_calls == []
|
||||||
@ -349,6 +354,7 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl,
|
|||||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
False,
|
False,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||||
@ -376,8 +382,8 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl,
|
|||||||
mock_ioctl.reset_mock()
|
mock_ioctl.reset_mock()
|
||||||
mock_pfctl.reset_mock()
|
mock_pfctl.reset_mock()
|
||||||
|
|
||||||
method.restore_firewall(1025, AF_INET, False, None)
|
method.restore_firewall(1025, AF_INET, False, None, None)
|
||||||
method.restore_firewall(1024, AF_INET6, False, None)
|
method.restore_firewall(1024, AF_INET6, False, None, None)
|
||||||
assert mock_ioctl.mock_calls == []
|
assert mock_ioctl.mock_calls == []
|
||||||
assert mock_pfctl.mock_calls == [
|
assert mock_pfctl.mock_calls == [
|
||||||
call('-a sshuttle-1025 -F all'),
|
call('-a sshuttle-1025 -F all'),
|
||||||
@ -408,6 +414,7 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||||
False,
|
False,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
|
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
@ -445,6 +452,7 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
True,
|
True,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||||
assert mock_pf_get_dev.mock_calls == []
|
assert mock_pf_get_dev.mock_calls == []
|
||||||
@ -459,6 +467,7 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
False,
|
False,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xcd60441a, ANY),
|
call(mock_pf_get_dev(), 0xcd60441a, ANY),
|
||||||
@ -484,8 +493,8 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
mock_ioctl.reset_mock()
|
mock_ioctl.reset_mock()
|
||||||
mock_pfctl.reset_mock()
|
mock_pfctl.reset_mock()
|
||||||
|
|
||||||
method.restore_firewall(1025, AF_INET, False, None)
|
method.restore_firewall(1025, AF_INET, False, None, None)
|
||||||
method.restore_firewall(1024, AF_INET6, False, None)
|
method.restore_firewall(1024, AF_INET6, False, None, None)
|
||||||
assert mock_ioctl.mock_calls == []
|
assert mock_ioctl.mock_calls == []
|
||||||
assert mock_pfctl.mock_calls == [
|
assert mock_pfctl.mock_calls == [
|
||||||
call('-a sshuttle-1025 -F all'),
|
call('-a sshuttle-1025 -F all'),
|
||||||
|
@ -78,7 +78,7 @@ def test_assert_features():
|
|||||||
|
|
||||||
def test_firewall_command():
|
def test_firewall_command():
|
||||||
method = get_method('tproxy')
|
method = get_method('tproxy')
|
||||||
assert not method.firewall_command("somthing")
|
assert not method.firewall_command("something")
|
||||||
|
|
||||||
|
|
||||||
@patch('sshuttle.methods.tproxy.ipt')
|
@patch('sshuttle.methods.tproxy.ipt')
|
||||||
@ -98,6 +98,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||||
True,
|
True,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(AF_INET6, 'mangle', 'sshuttle-m-1024'),
|
call(AF_INET6, 'mangle', 'sshuttle-m-1024'),
|
||||||
@ -122,6 +123,13 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
call(AF_INET6, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1024'),
|
call(AF_INET6, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1024'),
|
||||||
call(AF_INET6, 'mangle', '-I', 'PREROUTING', '1', '-j',
|
call(AF_INET6, 'mangle', '-I', 'PREROUTING', '1', '-j',
|
||||||
'sshuttle-t-1024'),
|
'sshuttle-t-1024'),
|
||||||
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
||||||
|
'--set-mark', '0x01', '--dest', u'2404:6800:4004:80c::33/32',
|
||||||
|
'-m', 'udp', '-p', 'udp', '--dport', '53'),
|
||||||
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
||||||
|
'--tproxy-mark', '0x01',
|
||||||
|
'--dest', u'2404:6800:4004:80c::33/32',
|
||||||
|
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1026'),
|
||||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
||||||
'-m', 'addrtype', '--dst-type', 'LOCAL'),
|
'-m', 'addrtype', '--dst-type', 'LOCAL'),
|
||||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
||||||
@ -133,13 +141,6 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
'-j', 'sshuttle-d-1024', '-m', 'tcp', '-p', 'tcp'),
|
'-j', 'sshuttle-d-1024', '-m', 'tcp', '-p', 'tcp'),
|
||||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
|
||||||
'-j', 'sshuttle-d-1024', '-m', 'udp', '-p', 'udp'),
|
'-j', 'sshuttle-d-1024', '-m', 'udp', '-p', 'udp'),
|
||||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
|
||||||
'--set-mark', '0x01', '--dest', u'2404:6800:4004:80c::33/32',
|
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '53'),
|
|
||||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
|
||||||
'--tproxy-mark', '0x01',
|
|
||||||
'--dest', u'2404:6800:4004:80c::33/32',
|
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1026'),
|
|
||||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
||||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8080:8080'),
|
'-m', 'tcp', '-p', 'tcp', '--dport', '8080:8080'),
|
||||||
@ -172,7 +173,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
mock_ipt_chain_exists.reset_mock()
|
mock_ipt_chain_exists.reset_mock()
|
||||||
mock_ipt.reset_mock()
|
mock_ipt.reset_mock()
|
||||||
|
|
||||||
method.restore_firewall(1025, AF_INET6, True, None)
|
method.restore_firewall(1025, AF_INET6, True, None, None)
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(AF_INET6, 'mangle', 'sshuttle-m-1025'),
|
call(AF_INET6, 'mangle', 'sshuttle-m-1025'),
|
||||||
call(AF_INET6, 'mangle', 'sshuttle-t-1025'),
|
call(AF_INET6, 'mangle', 'sshuttle-t-1025'),
|
||||||
@ -201,6 +202,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
True,
|
True,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
'0x01')
|
'0x01')
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(AF_INET, 'mangle', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', 'sshuttle-m-1025'),
|
||||||
@ -225,6 +227,12 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
call(AF_INET, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1025'),
|
||||||
call(AF_INET, 'mangle', '-I', 'PREROUTING', '1', '-j',
|
call(AF_INET, 'mangle', '-I', 'PREROUTING', '1', '-j',
|
||||||
'sshuttle-t-1025'),
|
'sshuttle-t-1025'),
|
||||||
|
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
|
||||||
|
'--set-mark', '0x01', '--dest', u'1.2.3.33/32',
|
||||||
|
'-m', 'udp', '-p', 'udp', '--dport', '53'),
|
||||||
|
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
|
||||||
|
'--tproxy-mark', '0x01', '--dest', u'1.2.3.33/32',
|
||||||
|
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1027'),
|
||||||
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
||||||
'-m', 'addrtype', '--dst-type', 'LOCAL'),
|
'-m', 'addrtype', '--dst-type', 'LOCAL'),
|
||||||
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
||||||
@ -236,12 +244,6 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
'-j', 'sshuttle-d-1025', '-m', 'tcp', '-p', 'tcp'),
|
'-j', 'sshuttle-d-1025', '-m', 'tcp', '-p', 'tcp'),
|
||||||
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
|
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
|
||||||
'-j', 'sshuttle-d-1025', '-m', 'udp', '-p', 'udp'),
|
'-j', 'sshuttle-d-1025', '-m', 'udp', '-p', 'udp'),
|
||||||
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
|
|
||||||
'--set-mark', '0x01', '--dest', u'1.2.3.33/32',
|
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '53'),
|
|
||||||
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
|
|
||||||
'--tproxy-mark', '0x01', '--dest', u'1.2.3.33/32',
|
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1027'),
|
|
||||||
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
||||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
|
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
|
||||||
'--dport', '80:80'),
|
'--dport', '80:80'),
|
||||||
@ -270,7 +272,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
|
|||||||
mock_ipt_chain_exists.reset_mock()
|
mock_ipt_chain_exists.reset_mock()
|
||||||
mock_ipt.reset_mock()
|
mock_ipt.reset_mock()
|
||||||
|
|
||||||
method.restore_firewall(1025, AF_INET, True, None)
|
method.restore_firewall(1025, AF_INET, True, None, None)
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(AF_INET, 'mangle', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', 'sshuttle-m-1025'),
|
||||||
call(AF_INET, 'mangle', 'sshuttle-t-1025'),
|
call(AF_INET, 'mangle', 'sshuttle-t-1025'),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import socket
|
import socket
|
||||||
from argparse import ArgumentTypeError as Fatal
|
from argparse import ArgumentTypeError as Fatal
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -27,6 +28,23 @@ _ip6_reprs = {
|
|||||||
_ip6_swidths = (48, 64, 96, 115, 128)
|
_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():
|
def test_parse_subnetport_ip4():
|
||||||
for ip_repr, ip in _ip4_reprs.items():
|
for ip_repr, ip in _ip4_reprs.items():
|
||||||
assert sshuttle.options.parse_subnetport(ip_repr) \
|
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():
|
def test_convert_arg_line_to_args_skips_comments():
|
||||||
parser = sshuttle.options.MyArgumentParser()
|
parser = sshuttle.options.MyArgumentParser()
|
||||||
assert parser.convert_arg_line_to_args("# whatever something") == []
|
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),
|
||||||
|
])
|
||||||
|
6
tox.ini
6
tox.ini
@ -1,17 +1,15 @@
|
|||||||
[tox]
|
[tox]
|
||||||
downloadcache = {toxworkdir}/cache/
|
downloadcache = {toxworkdir}/cache/
|
||||||
envlist =
|
envlist =
|
||||||
py36,
|
|
||||||
py37,
|
|
||||||
py38,
|
py38,
|
||||||
py39,
|
py39,
|
||||||
|
py310,
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
basepython =
|
basepython =
|
||||||
py36: python3.6
|
|
||||||
py37: python3.7
|
|
||||||
py38: python3.8
|
py38: python3.8
|
||||||
py39: python3.9
|
py39: python3.9
|
||||||
|
py310: python3.10
|
||||||
commands =
|
commands =
|
||||||
pip install -e .
|
pip install -e .
|
||||||
# actual flake8 test
|
# actual flake8 test
|
||||||
|
Reference in New Issue
Block a user