Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
sshuttle
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
pub
sshuttle
Commits
92b99442
Unverified
Commit
92b99442
authored
Nov 03, 2020
by
Brian May
Committed by
GitHub
Nov 03, 2020
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #551 from skuhl/which-fix
Improve consistency of PATH, environments, and which()
parents
b5aaeda2
709e5d15
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
128 additions
and
102 deletions
+128
-102
client.py
sshuttle/client.py
+11
-3
helpers.py
sshuttle/helpers.py
+73
-0
hostwatch.py
sshuttle/hostwatch.py
+4
-16
linux.py
sshuttle/linux.py
+4
-17
__init__.py
sshuttle/methods/__init__.py
+5
-14
ipfw.py
sshuttle/methods/ipfw.py
+6
-14
pf.py
sshuttle/methods/pf.py
+5
-9
server.py
sshuttle/server.py
+4
-26
ssh.py
sshuttle/ssh.py
+13
-1
test_methods_pf.py
tests/client/test_methods_pf.py
+3
-2
No files found.
sshuttle/client.py
View file @
92b99442
...
...
@@ -15,7 +15,7 @@ import sshuttle.ssyslog as ssyslog
import
sshuttle.sdnotify
as
sdnotify
from
sshuttle.ssnet
import
SockWrapper
,
Handler
,
Proxy
,
Mux
,
MuxWrapper
from
sshuttle.helpers
import
log
,
debug1
,
debug2
,
debug3
,
Fatal
,
islocal
,
\
resolvconf_nameservers
resolvconf_nameservers
,
which
from
sshuttle.methods
import
get_method
,
Features
from
sshuttle
import
__version__
try
:
...
...
@@ -196,11 +196,19 @@ class FirewallClient:
[
'--firewall'
])
if
ssyslog
.
_p
:
argvbase
+=
[
'--syslog'
]
# Default to sudo unless on OpenBSD in which case use built in `doas`
# Determine how to prefix the command in order to elevate privileges.
if
platform
.
platform
()
.
startswith
(
'OpenBSD'
):
elev_prefix
=
[
'doas'
]
elev_prefix
=
[
'doas'
]
# OpenBSD uses built in `doas`
else
:
elev_prefix
=
[
'sudo'
,
'-p'
,
'[local sudo] Password: '
]
# Look for binary and switch to absolute path if we can find
# it.
path
=
which
(
elev_prefix
[
0
])
if
path
:
elev_prefix
[
0
]
=
path
if
sudo_pythonpath
:
elev_prefix
+=
[
'/usr/bin/env'
,
'PYTHONPATH=
%
s'
%
python_path
]
...
...
sshuttle/helpers.py
View file @
92b99442
import
sys
import
socket
import
errno
import
os
logprefix
=
''
verbose
=
0
...
...
@@ -99,3 +100,75 @@ def family_to_string(family):
return
"AF_INET"
else
:
return
str
(
family
)
def
get_env
():
"""An environment for sshuttle subprocesses. See get_path()."""
env
=
{
'PATH'
:
get_path
(),
'LC_ALL'
:
"C"
,
}
return
env
def
get_path
():
"""Returns a string of paths separated by os.pathsep.
Users might not have all of the programs sshuttle needs in their
PATH variable (i.e., some programs might be in /sbin). Use PATH
and a hardcoded set of paths to search through. This function is
used by our which() and get_env() functions. If which() and the
subprocess environments differ, programs that which() finds might
not be found at run time (or vice versa).
"""
path
=
[]
if
"PATH"
in
os
.
environ
:
path
+=
os
.
environ
[
"PATH"
]
.
split
(
os
.
pathsep
)
# Python default paths.
path
+=
os
.
defpath
.
split
(
os
.
pathsep
)
# /sbin, etc are not in os.defpath and may not be in PATH either.
# /bin/ and /usr/bin below are probably redundant.
path
+=
[
'/bin'
,
'/usr/bin'
,
'/sbin'
,
'/usr/sbin'
]
# Remove duplicates. Not strictly necessary.
path_dedup
=
[]
for
i
in
path
:
if
i
not
in
path_dedup
:
path_dedup
.
append
(
i
)
return
os
.
pathsep
.
join
(
path_dedup
)
if
sys
.
version_info
>=
(
3
,
3
):
from
shutil
import
which
as
_which
else
:
# Although sshuttle does not officially support older versions of
# Python, some still run the sshuttle server on remote machines
# with old versions of python.
def
_which
(
file
,
mode
=
os
.
F_OK
|
os
.
X_OK
,
path
=
None
):
if
path
is
not
None
:
search_paths
=
path
.
split
(
os
.
pathsep
)
elif
"PATH"
in
os
.
environ
:
search_paths
=
os
.
environ
[
"PATH"
]
.
split
(
os
.
pathsep
)
else
:
search_paths
=
os
.
defpath
.
split
(
os
.
pathsep
)
for
p
in
search_paths
:
filepath
=
os
.
path
.
join
(
p
,
file
)
if
os
.
path
.
exists
(
filepath
)
and
os
.
access
(
filepath
,
mode
):
return
filepath
return
None
def
which
(
file
,
mode
=
os
.
F_OK
|
os
.
X_OK
):
"""A wrapper around shutil.which() that searches a predictable set of
paths and is more verbose about what is happening. See get_path()
for more information.
"""
path
=
get_path
()
rv
=
_which
(
file
,
mode
,
path
)
if
rv
:
debug2
(
"which() found '
%
s' at
%
s
\n
"
%
(
file
,
rv
))
else
:
debug2
(
"which() could not find '
%
s' in
%
s
\n
"
%
(
file
,
path
))
return
rv
sshuttle/hostwatch.py
View file @
92b99442
...
...
@@ -9,7 +9,7 @@ import platform
import
subprocess
as
ssubprocess
import
sshuttle.helpers
as
helpers
from
sshuttle.helpers
import
log
,
debug1
,
debug2
,
debug3
from
sshuttle.helpers
import
log
,
debug1
,
debug2
,
debug3
,
get_env
POLL_TIME
=
60
*
15
NETSTAT_POLL_TIME
=
30
...
...
@@ -125,14 +125,10 @@ def _check_dns(hostname):
def
_check_netstat
():
debug2
(
' > netstat
\n
'
)
env
=
{
'PATH'
:
os
.
environ
[
'PATH'
],
'LC_ALL'
:
"C"
,
}
argv
=
[
'netstat'
,
'-n'
]
try
:
p
=
ssubprocess
.
Popen
(
argv
,
stdout
=
ssubprocess
.
PIPE
,
stderr
=
null
,
env
=
env
)
env
=
get_env
()
)
content
=
p
.
stdout
.
read
()
.
decode
(
"ASCII"
)
p
.
wait
()
except
OSError
:
...
...
@@ -151,14 +147,10 @@ def _check_smb(hostname):
if
not
_smb_ok
:
return
debug2
(
' > smb:
%
s
\n
'
%
hostname
)
env
=
{
'PATH'
:
os
.
environ
[
'PATH'
],
'LC_ALL'
:
"C"
,
}
argv
=
[
'smbclient'
,
'-U'
,
'
%
'
,
'-L'
,
hostname
]
try
:
p
=
ssubprocess
.
Popen
(
argv
,
stdout
=
ssubprocess
.
PIPE
,
stderr
=
null
,
env
=
env
)
env
=
get_env
()
)
lines
=
p
.
stdout
.
readlines
()
p
.
wait
()
except
OSError
:
...
...
@@ -214,14 +206,10 @@ def _check_nmb(hostname, is_workgroup, is_master):
if
not
_nmb_ok
:
return
debug2
(
' > n
%
d
%
d:
%
s
\n
'
%
(
is_workgroup
,
is_master
,
hostname
))
env
=
{
'PATH'
:
os
.
environ
[
'PATH'
],
'LC_ALL'
:
"C"
,
}
argv
=
[
'nmblookup'
]
+
[
'-M'
]
*
is_master
+
[
'--'
,
hostname
]
try
:
p
=
ssubprocess
.
Popen
(
argv
,
stdout
=
ssubprocess
.
PIPE
,
stderr
=
null
,
env
=
env
)
env
=
get_
env
)
lines
=
p
.
stdout
.
readlines
()
rv
=
p
.
wait
()
except
OSError
:
...
...
sshuttle/linux.py
View file @
92b99442
import
os
import
socket
import
subprocess
as
ssubprocess
from
sshuttle.helpers
import
log
,
debug1
,
Fatal
,
family_to_string
from
sshuttle.helpers
import
log
,
debug1
,
Fatal
,
family_to_string
,
get_env
def
nonfatal
(
func
,
*
args
):
...
...
@@ -19,12 +18,8 @@ def ipt_chain_exists(family, table, name):
else
:
raise
Exception
(
'Unsupported family "
%
s"'
%
family_to_string
(
family
))
argv
=
[
cmd
,
'-t'
,
table
,
'-nL'
]
env
=
{
'PATH'
:
os
.
environ
[
'PATH'
],
'LC_ALL'
:
"C"
,
}
try
:
output
=
ssubprocess
.
check_output
(
argv
,
env
=
env
)
output
=
ssubprocess
.
check_output
(
argv
,
env
=
get_env
()
)
for
line
in
output
.
decode
(
'ASCII'
)
.
split
(
'
\n
'
):
if
line
.
startswith
(
'Chain
%
s '
%
name
):
return
True
...
...
@@ -40,11 +35,7 @@ def ipt(family, table, *args):
else
:
raise
Exception
(
'Unsupported family "
%
s"'
%
family_to_string
(
family
))
debug1
(
'
%
s
\n
'
%
' '
.
join
(
argv
))
env
=
{
'PATH'
:
os
.
environ
[
'PATH'
],
'LC_ALL'
:
"C"
,
}
rv
=
ssubprocess
.
call
(
argv
,
env
=
env
)
rv
=
ssubprocess
.
call
(
argv
,
env
=
get_env
())
if
rv
:
raise
Fatal
(
'fw:
%
r returned
%
d'
%
(
argv
,
rv
))
...
...
@@ -55,11 +46,7 @@ def nft(family, table, action, *args):
else
:
raise
Exception
(
'Unsupported family "
%
s"'
%
family_to_string
(
family
))
debug1
(
'
%
s
\n
'
%
' '
.
join
(
argv
))
env
=
{
'PATH'
:
os
.
environ
[
'PATH'
],
'LC_ALL'
:
"C"
,
}
rv
=
ssubprocess
.
call
(
argv
,
env
=
env
)
rv
=
ssubprocess
.
call
(
argv
,
env
=
get_env
())
if
rv
:
raise
Fatal
(
'fw:
%
r returned
%
d'
%
(
argv
,
rv
))
...
...
sshuttle/methods/__init__.py
View file @
92b99442
import
os
import
importlib
import
socket
import
struct
import
errno
import
ipaddress
from
sshuttle.helpers
import
Fatal
,
debug3
from
sshuttle.helpers
import
Fatal
,
debug3
,
which
def
original_dst
(
sock
):
...
...
@@ -97,27 +96,19 @@ class BaseMethod(object):
return
False
def
_program_exists
(
name
):
paths
=
(
os
.
getenv
(
'PATH'
)
or
os
.
defpath
)
.
split
(
os
.
pathsep
)
for
p
in
paths
:
fn
=
'
%
s/
%
s'
%
(
p
,
name
)
if
os
.
path
.
exists
(
fn
):
return
not
os
.
path
.
isdir
(
fn
)
and
os
.
access
(
fn
,
os
.
X_OK
)
def
get_method
(
method_name
):
module
=
importlib
.
import_module
(
"sshuttle.methods.
%
s"
%
method_name
)
return
module
.
Method
(
method_name
)
def
get_auto_method
():
if
_program_exists
(
'iptables'
):
if
which
(
'iptables'
):
method_name
=
"nat"
elif
_program_exists
(
'nft'
):
elif
which
(
'nft'
):
method_name
=
"nft"
elif
_program_exists
(
'pfctl'
):
elif
which
(
'pfctl'
):
method_name
=
"pf"
elif
_program_exists
(
'ipfw'
):
elif
which
(
'ipfw'
):
method_name
=
"ipfw"
else
:
raise
Fatal
(
...
...
sshuttle/methods/ipfw.py
View file @
92b99442
...
...
@@ -2,7 +2,7 @@ import os
import
subprocess
as
ssubprocess
from
sshuttle.methods
import
BaseMethod
from
sshuttle.helpers
import
log
,
debug1
,
debug3
,
\
Fatal
,
family_to_string
Fatal
,
family_to_string
,
get_env
recvmsg
=
None
try
:
...
...
@@ -61,11 +61,7 @@ else:
def
ipfw_rule_exists
(
n
):
argv
=
[
'ipfw'
,
'list'
]
env
=
{
'PATH'
:
os
.
environ
[
'PATH'
],
'LC_ALL'
:
"C"
,
}
p
=
ssubprocess
.
Popen
(
argv
,
stdout
=
ssubprocess
.
PIPE
,
env
=
env
)
p
=
ssubprocess
.
Popen
(
argv
,
stdout
=
ssubprocess
.
PIPE
,
env
=
get_env
())
found
=
False
for
line
in
p
.
stdout
:
...
...
@@ -85,11 +81,7 @@ _oldctls = {}
def
_fill_oldctls
(
prefix
):
argv
=
[
'sysctl'
,
prefix
]
env
=
{
'PATH'
:
os
.
environ
[
'PATH'
],
'LC_ALL'
:
"C"
,
}
p
=
ssubprocess
.
Popen
(
argv
,
stdout
=
ssubprocess
.
PIPE
,
env
=
env
)
p
=
ssubprocess
.
Popen
(
argv
,
stdout
=
ssubprocess
.
PIPE
,
env
=
get_env
())
for
line
in
p
.
stdout
:
line
=
line
.
decode
()
assert
(
line
[
-
1
]
==
'
\n
'
)
...
...
@@ -105,7 +97,7 @@ def _fill_oldctls(prefix):
def
_sysctl_set
(
name
,
val
):
argv
=
[
'sysctl'
,
'-w'
,
'
%
s=
%
s'
%
(
name
,
val
)]
debug1
(
'>>
%
s
\n
'
%
' '
.
join
(
argv
))
return
ssubprocess
.
call
(
argv
,
stdout
=
open
(
os
.
devnull
,
'w'
))
return
ssubprocess
.
call
(
argv
,
stdout
=
open
(
os
.
devnull
,
'w'
)
,
env
=
get_env
()
)
# No env: No output. (Or error that won't be parsed.)
...
...
@@ -139,7 +131,7 @@ def sysctl_set(name, val, permanent=False):
def
ipfw
(
*
args
):
argv
=
[
'ipfw'
,
'-q'
]
+
list
(
args
)
debug1
(
'>>
%
s
\n
'
%
' '
.
join
(
argv
))
rv
=
ssubprocess
.
call
(
argv
)
rv
=
ssubprocess
.
call
(
argv
,
env
=
get_env
()
)
# No env: No output. (Or error that won't be parsed.)
if
rv
:
raise
Fatal
(
'
%
r returned
%
d'
%
(
argv
,
rv
))
...
...
@@ -148,7 +140,7 @@ def ipfw(*args):
def
ipfw_noexit
(
*
args
):
argv
=
[
'ipfw'
,
'-q'
]
+
list
(
args
)
debug1
(
'>>
%
s
\n
'
%
' '
.
join
(
argv
))
ssubprocess
.
call
(
argv
)
ssubprocess
.
call
(
argv
,
env
=
get_env
()
)
# No env: No output. (Or error that won't be parsed.)
...
...
sshuttle/methods/pf.py
View file @
92b99442
...
...
@@ -11,7 +11,8 @@ from fcntl import ioctl
from
ctypes
import
c_char
,
c_uint8
,
c_uint16
,
c_uint32
,
Union
,
Structure
,
\
sizeof
,
addressof
,
memmove
from
sshuttle.firewall
import
subnet_weight
from
sshuttle.helpers
import
debug1
,
debug2
,
debug3
,
Fatal
,
family_to_string
from
sshuttle.helpers
import
debug1
,
debug2
,
debug3
,
Fatal
,
family_to_string
,
\
get_env
from
sshuttle.methods
import
BaseMethod
...
...
@@ -179,7 +180,7 @@ class FreeBsd(Generic):
return
freebsd
def
enable
(
self
):
returncode
=
ssubprocess
.
call
([
'kldload'
,
'pf'
])
returncode
=
ssubprocess
.
call
([
'kldload'
,
'pf'
]
,
env
=
get_env
()
)
# No env: No output.
super
(
FreeBsd
,
self
)
.
enable
()
if
returncode
==
0
:
...
...
@@ -189,7 +190,7 @@ class FreeBsd(Generic):
super
(
FreeBsd
,
self
)
.
disable
(
anchor
)
if
_pf_context
[
'loaded_by_sshuttle'
]
and
\
_pf_context
[
'started_by_sshuttle'
]
==
0
:
ssubprocess
.
call
([
'kldunload'
,
'pf'
])
ssubprocess
.
call
([
'kldunload'
,
'pf'
]
,
env
=
get_env
()
)
# No env: No output.
def
add_anchors
(
self
,
anchor
):
...
...
@@ -386,15 +387,10 @@ else:
def
pfctl
(
args
,
stdin
=
None
):
argv
=
[
'pfctl'
]
+
shlex
.
split
(
args
)
debug1
(
'>>
%
s
\n
'
%
' '
.
join
(
argv
))
env
=
{
'PATH'
:
os
.
environ
[
'PATH'
],
'LC_ALL'
:
"C"
,
}
p
=
ssubprocess
.
Popen
(
argv
,
stdin
=
ssubprocess
.
PIPE
,
stdout
=
ssubprocess
.
PIPE
,
stderr
=
ssubprocess
.
PIPE
,
env
=
env
)
env
=
get_env
()
)
o
=
p
.
communicate
(
stdin
)
if
p
.
returncode
:
raise
Fatal
(
'
%
r returned
%
d'
%
(
argv
,
p
.
returncode
))
...
...
sshuttle/server.py
View file @
92b99442
...
...
@@ -7,24 +7,6 @@ import sys
import
os
import
platform
if
sys
.
version_info
>=
(
3
,
0
):
from
shutil
import
which
else
:
# python2 compat: shutil.which is not available so we provide our
# own which command
def
which
(
file
,
mode
=
os
.
F_OK
|
os
.
X_OK
,
path
=
None
):
if
path
is
not
None
:
search_paths
=
[
path
]
elif
"PATH"
in
os
.
environ
:
search_paths
=
os
.
environ
[
"PATH"
]
.
split
(
os
.
pathsep
)
else
:
search_paths
=
os
.
defpath
.
split
(
os
.
pathsep
)
for
p
in
search_paths
:
filepath
=
os
.
path
.
join
(
p
,
file
)
if
os
.
path
.
exists
(
filepath
)
and
os
.
access
(
filepath
,
mode
):
return
filepath
return
None
import
sshuttle.ssnet
as
ssnet
import
sshuttle.helpers
as
helpers
...
...
@@ -32,7 +14,7 @@ import sshuttle.hostwatch as hostwatch
import
subprocess
as
ssubprocess
from
sshuttle.ssnet
import
Handler
,
Proxy
,
Mux
,
MuxWrapper
from
sshuttle.helpers
import
b
,
log
,
debug1
,
debug2
,
debug3
,
Fatal
,
\
resolvconf_random_nameserver
resolvconf_random_nameserver
,
which
,
get_env
def
_ipmatch
(
ipstr
):
...
...
@@ -100,11 +82,7 @@ def _route_iproute(line):
def
_list_routes
(
argv
,
extract_route
):
# FIXME: IPv4 only
env
=
{
'PATH'
:
os
.
environ
[
'PATH'
],
'LC_ALL'
:
"C"
,
}
p
=
ssubprocess
.
Popen
(
argv
,
stdout
=
ssubprocess
.
PIPE
,
env
=
env
)
p
=
ssubprocess
.
Popen
(
argv
,
stdout
=
ssubprocess
.
PIPE
,
env
=
get_env
())
routes
=
[]
for
line
in
p
.
stdout
:
if
not
line
.
strip
():
...
...
@@ -119,7 +97,6 @@ def _list_routes(argv, extract_route):
rv
=
p
.
wait
()
if
rv
!=
0
:
log
(
'WARNING:
%
r returned
%
d
\n
'
%
(
argv
,
rv
))
log
(
'WARNING: That prevents --auto-nets from working.
\n
'
)
return
routes
...
...
@@ -130,7 +107,8 @@ def list_routes():
elif
which
(
'netstat'
):
routes
=
_list_routes
([
'netstat'
,
'-rn'
],
_route_netstat
)
else
:
log
(
'WARNING: Neither ip nor netstat were found on the server.
\n
'
)
log
(
'WARNING: Neither "ip" nor "netstat" were found on the server. '
'--auto-nets feature will not work.
\n
'
)
routes
=
[]
for
(
family
,
ip
,
width
)
in
routes
:
...
...
sshuttle/ssh.py
View file @
92b99442
...
...
@@ -12,7 +12,7 @@ import ipaddress
from
urllib.parse
import
urlparse
import
sshuttle.helpers
as
helpers
from
sshuttle.helpers
import
debug2
from
sshuttle.helpers
import
debug2
,
which
,
get_path
,
Fatal
def
get_module_source
(
name
):
...
...
@@ -141,6 +141,17 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
argv
=
(
sshl
+
portl
+
[
rhost
,
'--'
,
pycmd
])
# Our which() function searches for programs in get_path()
# directories (which include PATH). This step isn't strictly
# necessary if ssh is already in the user's PATH, but it makes the
# error message friendlier if the user incorrectly passes in a
# custom ssh command that we cannot find.
abs_path
=
which
(
argv
[
0
])
if
abs_path
is
None
:
raise
Fatal
(
"Failed to find '
%
s' in path
%
s"
%
(
argv
[
0
],
get_path
()))
argv
[
0
]
=
abs_path
(
s1
,
s2
)
=
socket
.
socketpair
()
def
setup
():
...
...
@@ -148,6 +159,7 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
s2
.
close
()
s1a
,
s1b
=
os
.
dup
(
s1
.
fileno
()),
os
.
dup
(
s1
.
fileno
())
s1
.
close
()
debug2
(
'executing:
%
r
\n
'
%
argv
)
p
=
ssubprocess
.
Popen
(
argv
,
stdin
=
s1a
,
stdout
=
s1b
,
preexec_fn
=
setup
,
close_fds
=
True
,
stderr
=
stderr
)
...
...
tests/client/test_methods_pf.py
View file @
92b99442
...
...
@@ -4,7 +4,7 @@ from socket import AF_INET, AF_INET6
import
pytest
from
mock
import
Mock
,
patch
,
call
,
ANY
from
sshuttle.methods
import
get_method
from
sshuttle.helpers
import
Fatal
from
sshuttle.helpers
import
Fatal
,
get_env
from
sshuttle.methods.pf
import
FreeBsd
,
Darwin
,
OpenBsd
...
...
@@ -316,7 +316,8 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl,
b
'to <dns_servers> port 53 keep state
\n
'
),
call
(
'-e'
),
]
assert
call
([
'kldload'
,
'pf'
])
in
mock_subprocess_call
.
mock_calls
assert
call
([
'kldload'
,
'pf'
],
env
=
get_env
())
in
\
mock_subprocess_call
.
mock_calls
mock_pf_get_dev
.
reset_mock
()
mock_ioctl
.
reset_mock
()
mock_pfctl
.
reset_mock
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment