mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-07-03 16:21:17 +02:00
Compare commits
18 Commits
dns
...
sshuttle-0
Author | SHA1 | Date | |
---|---|---|---|
a8b71f6387 | |||
4bfcd7091d | |||
bd489b3319 | |||
8ab5ef283d | |||
e67208a294 | |||
7859be13c2 | |||
f313d50690 | |||
15e26d2e0e | |||
e2ec475de3 | |||
57e744dadd | |||
c13be0b817 | |||
da2c6273f6 | |||
7712c60c36 | |||
65b0390fe9 | |||
c5834a9773 | |||
e2474543fc | |||
8636378870 | |||
f5eed4c809 |
14
README.md
14
README.md
@ -63,7 +63,19 @@ This is how you use it:
|
||||
on your client machine. You'll need root or sudo
|
||||
access, and python needs to be installed.
|
||||
|
||||
- <tt>./sshuttle -r username@sshserver 0.0.0.0/0 -vv</tt>
|
||||
- The most basic use of sshuttle looks like:
|
||||
<tt>./sshuttle -r username@sshserver 0.0.0.0/0 -vv</tt>
|
||||
|
||||
- There is a shortcut for 0.0.0.0/0 for those that value
|
||||
their wrists
|
||||
<tt>./sshuttle -r username@sshserver 0/0 -vv</tt>
|
||||
|
||||
- If you would also like your DNS queries to be proxied
|
||||
through the DNS server of the server you are connect to:
|
||||
<tt>./sshuttle --dns -vvr username@sshserver 0/0</tt>
|
||||
|
||||
The above is probably what you want to use to prevent
|
||||
local network attacks such as Firesheep and friends.
|
||||
|
||||
(You may be prompted for one or more passwords; first, the
|
||||
local password to become root using either sudo or su, and
|
||||
|
123
client.py
123
client.py
@ -175,6 +175,65 @@ class FirewallClient:
|
||||
raise Fatal('cleanup: %r returned %d' % (self.argv, rv))
|
||||
|
||||
|
||||
def onaccept(listener, mux, handlers):
|
||||
global _extra_fd
|
||||
try:
|
||||
sock,srcip = listener.accept()
|
||||
except socket.error, e:
|
||||
if e.args[0] in [errno.EMFILE, errno.ENFILE]:
|
||||
debug1('Rejected incoming connection: too many open files!\n')
|
||||
# free up an fd so we can eat the connection
|
||||
os.close(_extra_fd)
|
||||
try:
|
||||
sock,srcip = listener.accept()
|
||||
sock.close()
|
||||
finally:
|
||||
_extra_fd = os.open('/dev/null', os.O_RDONLY)
|
||||
return
|
||||
else:
|
||||
raise
|
||||
dstip = original_dst(sock)
|
||||
debug1('Accept: %s:%r -> %s:%r.\n' % (srcip[0],srcip[1],
|
||||
dstip[0],dstip[1]))
|
||||
if dstip[1] == listener.getsockname()[1] and islocal(dstip[0]):
|
||||
debug1("-- ignored: that's my address!\n")
|
||||
sock.close()
|
||||
return
|
||||
chan = mux.next_channel()
|
||||
if not chan:
|
||||
log('warning: too many open channels. Discarded connection.\n')
|
||||
sock.close()
|
||||
return
|
||||
mux.send(chan, ssnet.CMD_CONNECT, '%s,%s' % dstip)
|
||||
outwrap = MuxWrapper(mux, chan)
|
||||
handlers.append(Proxy(SockWrapper(sock, sock), outwrap))
|
||||
|
||||
|
||||
dnsreqs = {}
|
||||
def dns_done(chan, data):
|
||||
peer,sock,timeout = dnsreqs.get(chan) or (None,None,None)
|
||||
debug3('dns_done: channel=%r peer=%r\n' % (chan, peer))
|
||||
if peer:
|
||||
del dnsreqs[chan]
|
||||
debug3('doing sendto %r\n' % (peer,))
|
||||
sock.sendto(data, peer)
|
||||
|
||||
|
||||
def ondns(listener, mux, handlers):
|
||||
pkt,peer = listener.recvfrom(4096)
|
||||
now = time.time()
|
||||
if pkt:
|
||||
debug1('DNS request from %r: %d bytes\n' % (peer, len(pkt)))
|
||||
chan = mux.next_channel()
|
||||
dnsreqs[chan] = peer,listener,now+30
|
||||
mux.send(chan, ssnet.CMD_DNS_REQ, pkt)
|
||||
mux.channels[chan] = lambda cmd,data: dns_done(chan,data)
|
||||
for chan,(peer,sock,timeout) in dnsreqs.items():
|
||||
if timeout < now:
|
||||
del dnsreqs[chan]
|
||||
debug3('Remaining DNS requests: %d\n' % len(dnsreqs))
|
||||
|
||||
|
||||
def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
|
||||
dnslistener, seed_hosts, auto_nets,
|
||||
syslog, daemon):
|
||||
@ -198,7 +257,14 @@ def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
|
||||
handlers.append(mux)
|
||||
|
||||
expected = 'SSHUTTLE0001'
|
||||
|
||||
try:
|
||||
v = 'x'
|
||||
while v and v != '\0':
|
||||
v = serversock.recv(1)
|
||||
v = 'x'
|
||||
while v and v != '\0':
|
||||
v = serversock.recv(1)
|
||||
initstring = serversock.recv(len(expected))
|
||||
except socket.error, e:
|
||||
if e.args[0] == errno.ECONNRESET:
|
||||
@ -248,63 +314,10 @@ def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
|
||||
fw.sethostip(name, ip)
|
||||
mux.got_host_list = onhostlist
|
||||
|
||||
def onaccept():
|
||||
global _extra_fd
|
||||
try:
|
||||
sock,srcip = listener.accept()
|
||||
except socket.error, e:
|
||||
if e.args[0] in [errno.EMFILE, errno.ENFILE]:
|
||||
debug1('Rejected incoming connection: too many open files!\n')
|
||||
# free up an fd so we can eat the connection
|
||||
os.close(_extra_fd)
|
||||
try:
|
||||
sock,srcip = listener.accept()
|
||||
sock.close()
|
||||
finally:
|
||||
_extra_fd = os.open('/dev/null', os.O_RDONLY)
|
||||
return
|
||||
else:
|
||||
raise
|
||||
dstip = original_dst(sock)
|
||||
debug1('Accept: %s:%r -> %s:%r.\n' % (srcip[0],srcip[1],
|
||||
dstip[0],dstip[1]))
|
||||
if dstip[1] == listener.getsockname()[1] and islocal(dstip[0]):
|
||||
debug1("-- ignored: that's my address!\n")
|
||||
sock.close()
|
||||
return
|
||||
chan = mux.next_channel()
|
||||
if not chan:
|
||||
log('warning: too many open channels. Discarded connection.\n')
|
||||
sock.close()
|
||||
return
|
||||
mux.send(chan, ssnet.CMD_CONNECT, '%s,%s' % dstip)
|
||||
outwrap = MuxWrapper(mux, chan)
|
||||
handlers.append(Proxy(SockWrapper(sock, sock), outwrap))
|
||||
handlers.append(Handler([listener], onaccept))
|
||||
handlers.append(Handler([listener], lambda: onaccept(listener, mux, handlers)))
|
||||
|
||||
dnsreqs = {}
|
||||
def dns_done(chan, data):
|
||||
peer,timeout = dnsreqs.get(chan) or (None,None)
|
||||
debug3('dns_done: channel=%r peer=%r\n' % (chan, peer))
|
||||
if peer:
|
||||
del dnsreqs[chan]
|
||||
debug3('doing sendto %r\n' % (peer,))
|
||||
dnslistener.sendto(data, peer)
|
||||
def ondns():
|
||||
pkt,peer = dnslistener.recvfrom(4096)
|
||||
now = time.time()
|
||||
if pkt:
|
||||
debug1('DNS request from %r: %d bytes\n' % (peer, len(pkt)))
|
||||
chan = mux.next_channel()
|
||||
dnsreqs[chan] = peer,now+30
|
||||
mux.send(chan, ssnet.CMD_DNS_REQ, pkt)
|
||||
mux.channels[chan] = lambda cmd,data: dns_done(chan,data)
|
||||
for chan,(peer,timeout) in dnsreqs.items():
|
||||
if timeout < now:
|
||||
del dnsreqs[chan]
|
||||
debug3('Remaining DNS requests: %d\n' % len(dnsreqs))
|
||||
if dnslistener:
|
||||
handlers.append(Handler([dnslistener], ondns))
|
||||
handlers.append(Handler([dnslistener], lambda: ondns(dnslistener, mux, handlers)))
|
||||
|
||||
if seed_hosts != None:
|
||||
debug1('seed_hosts: %r\n' % seed_hosts)
|
||||
|
115
do
115
do
@ -8,14 +8,14 @@
|
||||
#
|
||||
|
||||
# By default, no output coloring.
|
||||
GREEN=""
|
||||
BOLD=""
|
||||
PLAIN=""
|
||||
green=""
|
||||
bold=""
|
||||
plain=""
|
||||
|
||||
if [ -n "$TERM" -a "$TERM" != "dumb" ] && tty <&2 >/dev/null 2>&1; then
|
||||
GREEN="$(printf '\033[32m')"
|
||||
BOLD="$(printf '\033[1m')"
|
||||
PLAIN="$(printf '\033[m')"
|
||||
green="$(printf '\033[32m')"
|
||||
bold="$(printf '\033[1m')"
|
||||
plain="$(printf '\033[m')"
|
||||
fi
|
||||
|
||||
_dirsplit()
|
||||
@ -24,6 +24,13 @@ _dirsplit()
|
||||
dir=${1%$base}
|
||||
}
|
||||
|
||||
dirname()
|
||||
(
|
||||
_dirsplit "$1"
|
||||
dir=${dir%/}
|
||||
echo "${dir:-.}"
|
||||
)
|
||||
|
||||
_dirsplit "$0"
|
||||
export REDO=$(cd "${dir:-.}" && echo "$PWD/$base")
|
||||
|
||||
@ -54,87 +61,105 @@ fi
|
||||
|
||||
_find_dofile_pwd()
|
||||
{
|
||||
DOFILE=default.$1.do
|
||||
dofile=default.$1.do
|
||||
while :; do
|
||||
DOFILE=default.${DOFILE#default.*.}
|
||||
[ -e "$DOFILE" -o "$DOFILE" = default.do ] && break
|
||||
dofile=default.${dofile#default.*.}
|
||||
[ -e "$dofile" -o "$dofile" = default.do ] && break
|
||||
done
|
||||
EXT=${DOFILE#default}
|
||||
EXT=${EXT%.do}
|
||||
BASE=${1%$EXT}
|
||||
ext=${dofile#default}
|
||||
ext=${ext%.do}
|
||||
base=${1%$ext}
|
||||
}
|
||||
|
||||
|
||||
_find_dofile()
|
||||
{
|
||||
PREFIX=
|
||||
local prefix=
|
||||
while :; do
|
||||
_find_dofile_pwd "$1"
|
||||
[ -e "$DOFILE" ] && break
|
||||
[ -e "$dofile" ] && break
|
||||
[ "$PWD" = "/" ] && break
|
||||
TARGET=${PWD##*/}/$TARGET
|
||||
PREFIX=${PWD##*/}/$PREFIX
|
||||
target=${PWD##*/}/$target
|
||||
tmp=${PWD##*/}/$tmp
|
||||
prefix=${PWD##*/}/$prefix
|
||||
cd ..
|
||||
done
|
||||
BASE=$PREFIX$BASE
|
||||
base=$prefix$base
|
||||
}
|
||||
|
||||
|
||||
_run_dofile()
|
||||
{
|
||||
export DO_DEPTH="$DO_DEPTH "
|
||||
export REDO_TARGET=$PWD/$TARGET
|
||||
export REDO_TARGET=$PWD/$target
|
||||
local line1
|
||||
set -e
|
||||
read line1 <"$PWD/$DOFILE"
|
||||
read line1 <"$PWD/$dofile"
|
||||
cmd=${line1#"#!/"}
|
||||
if [ "$cmd" != "$line1" ]; then
|
||||
/$cmd "$PWD/$DOFILE" "$@" >"$TARGET.tmp2"
|
||||
/$cmd "$PWD/$dofile" "$@" >"$tmp.tmp2"
|
||||
else
|
||||
. "$PWD/$DOFILE" >"$TARGET.tmp2"
|
||||
:; . "$PWD/$dofile" >"$tmp.tmp2"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
_do()
|
||||
{
|
||||
DIR=$1
|
||||
TARGET=$2
|
||||
if [ ! -e "$TARGET" ] || [ -e "$TARGET/." -a ! -e "$TARGET.did" ]; then
|
||||
local dir=$1 target=$2 tmp=$3
|
||||
if [ ! -e "$target" ] || [ -d "$target" -a ! -e "$target.did" ]; then
|
||||
printf '%sdo %s%s%s%s\n' \
|
||||
"$GREEN" "$DO_DEPTH" "$BOLD" "$DIR$TARGET" "$PLAIN" >&2
|
||||
echo "$PWD/$TARGET" >>"$DO_BUILT"
|
||||
DOFILE=$TARGET.do
|
||||
BASE=$TARGET
|
||||
EXT=
|
||||
[ -e "$TARGET.do" ] || _find_dofile "$TARGET"
|
||||
if [ ! -e "$DOFILE" ]; then
|
||||
echo "do: $TARGET: no .do file" >&2
|
||||
"$green" "$DO_DEPTH" "$bold" "$dir$target" "$plain" >&2
|
||||
echo "$PWD/$target" >>"$DO_BUILT"
|
||||
dofile=$target.do
|
||||
base=$target
|
||||
ext=
|
||||
[ -e "$target.do" ] || _find_dofile "$target"
|
||||
if [ ! -e "$dofile" ]; then
|
||||
echo "do: $target: no .do file" >&2
|
||||
return 1
|
||||
fi
|
||||
[ ! -e "$DO_BUILD" ] || : >>"$TARGET.did"
|
||||
( _run_dofile "$BASE" "$EXT" "$TARGET.tmp" )
|
||||
RV=$?
|
||||
if [ $RV != 0 ]; then
|
||||
[ ! -e "$DO_BUILT" ] || [ ! -d "$(dirname "$target")" ] ||
|
||||
: >>"$target.did"
|
||||
( _run_dofile "$base" "$ext" "$tmp.tmp" )
|
||||
rv=$?
|
||||
if [ $rv != 0 ]; then
|
||||
printf "do: %s%s\n" "$DO_DEPTH" \
|
||||
"$DIR$TARGET: got exit code $RV" >&2
|
||||
rm -f "$TARGET.tmp" "$TARGET.tmp2"
|
||||
return $RV
|
||||
"$dir$target: got exit code $rv" >&2
|
||||
rm -f "$tmp.tmp" "$tmp.tmp2"
|
||||
return $rv
|
||||
fi
|
||||
mv "$TARGET.tmp" "$TARGET" 2>/dev/null ||
|
||||
! test -s "$TARGET.tmp2" ||
|
||||
mv "$TARGET.tmp2" "$TARGET" 2>/dev/null
|
||||
rm -f "$TARGET.tmp2"
|
||||
mv "$tmp.tmp" "$target" 2>/dev/null ||
|
||||
! test -s "$tmp.tmp2" ||
|
||||
mv "$tmp.tmp2" "$target" 2>/dev/null
|
||||
rm -f "$tmp.tmp2"
|
||||
else
|
||||
echo "do $DO_DEPTH$TARGET exists." >&2
|
||||
echo "do $DO_DEPTH$target exists." >&2
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Make corrections for directories that don't actually exist yet.
|
||||
_dir_shovel()
|
||||
{
|
||||
local dir base
|
||||
xdir=$1 xbase=$2 xbasetmp=$2
|
||||
while [ ! -d "$xdir" -a -n "$xdir" ]; do
|
||||
_dirsplit "${xdir%/}"
|
||||
xbasetmp=${base}__$xbase
|
||||
xdir=$dir xbase=$base/$xbase
|
||||
echo "xbasetmp='$xbasetmp'" >&2
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
redo()
|
||||
{
|
||||
for i in "$@"; do
|
||||
_dirsplit "$i"
|
||||
( cd "$dir" && _do "$dir" "$base" ) || return 1
|
||||
_dir_shovel "$dir" "$base"
|
||||
dir=$xdir base=$xbase basetmp=$xbasetmp
|
||||
( cd "$dir" && _do "$dir" "$base" "$basetmp" ) || return 1
|
||||
done
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import sys, os, socket
|
||||
import sys, os, socket, errno
|
||||
|
||||
logprefix = ''
|
||||
verbose = 0
|
||||
|
@ -130,7 +130,7 @@ class DnsProxy(Handler):
|
||||
try:
|
||||
self.sock.send(self.request)
|
||||
except socket.error, e:
|
||||
if e.args[0] in [errno.ECONNREFUSED, errno.EHOSTUNREACH]:
|
||||
if e.args[0] in ssnet.NET_ERRS:
|
||||
# might have been spurious; try again.
|
||||
# Note: these errors sometimes are reported by recv(),
|
||||
# and sometimes by send(). We have to catch both.
|
||||
@ -145,7 +145,7 @@ class DnsProxy(Handler):
|
||||
try:
|
||||
data = self.sock.recv(4096)
|
||||
except socket.error, e:
|
||||
if e.args[0] in [errno.ECONNREFUSED, errno.EHOSTUNREACH]:
|
||||
if e.args[0] in ssnet.NET_ERRS:
|
||||
# might have been spurious; try again.
|
||||
# Note: these errors sometimes are reported by recv(),
|
||||
# and sometimes by send(). We have to catch both.
|
||||
@ -173,7 +173,7 @@ def main():
|
||||
debug1(' %s/%d\n' % r)
|
||||
|
||||
# synchronization header
|
||||
sys.stdout.write('SSHUTTLE0001')
|
||||
sys.stdout.write('\0\0SSHUTTLE0001')
|
||||
sys.stdout.flush()
|
||||
|
||||
handlers = []
|
||||
|
2
ssh.py
2
ssh.py
@ -85,7 +85,7 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
|
||||
pycmd = "'%s' -c '%s'" % (python, pyscript)
|
||||
else:
|
||||
pycmd = ("P=python2; $P -V 2>/dev/null || P=python; "
|
||||
"\"$P\" -c '%s'") % pyscript
|
||||
"exec \"$P\" -c '%s'") % pyscript
|
||||
argv = (sshl +
|
||||
portl +
|
||||
ipv6flag +
|
||||
|
7
sshuttle
7
sshuttle
@ -1,5 +1,10 @@
|
||||
#!/bin/sh
|
||||
DIR=$(dirname "$0")
|
||||
EXE=$0
|
||||
for i in 1 2 3 4 5 6 7 8 9 10; do
|
||||
[ -L "$EXE" ] || break
|
||||
EXE=$(readlink "$EXE")
|
||||
done
|
||||
DIR=$(dirname "$EXE")
|
||||
if python2 -V 2>/dev/null; then
|
||||
exec python2 "$DIR/main.py" python2 "$@"
|
||||
else
|
||||
|
27
ssnet.py
27
ssnet.py
@ -40,7 +40,11 @@ cmd_to_name = {
|
||||
CMD_DNS_REQ: 'DNS_REQ',
|
||||
CMD_DNS_RESPONSE: 'DNS_RESPONSE',
|
||||
}
|
||||
|
||||
|
||||
|
||||
NET_ERRS = [errno.ECONNREFUSED, errno.ETIMEDOUT,
|
||||
errno.EHOSTUNREACH, errno.ENETUNREACH,
|
||||
errno.EHOSTDOWN, errno.ENETDOWN]
|
||||
|
||||
|
||||
def _add(l, elem):
|
||||
@ -124,6 +128,12 @@ class SockWrapper:
|
||||
return # already connected
|
||||
self.rsock.setblocking(False)
|
||||
debug3('%r: trying connect to %r\n' % (self, self.connect_to))
|
||||
if socket.inet_aton(self.connect_to[0])[0] == '\0':
|
||||
self.seterr(Exception("Can't connect to %r: "
|
||||
"IP address starts with zero\n"
|
||||
% (self.connect_to,)))
|
||||
self.connect_to = None
|
||||
return
|
||||
try:
|
||||
self.rsock.connect(self.connect_to)
|
||||
# connected successfully (Linux)
|
||||
@ -142,12 +152,21 @@ class SockWrapper:
|
||||
debug3('%r: fixed connect result: %s\n' % (self, e))
|
||||
if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]:
|
||||
pass # not connected yet
|
||||
elif e.args[0] == 0:
|
||||
# connected successfully (weird Linux bug?)
|
||||
# Sometimes Linux seems to return EINVAL when it isn't
|
||||
# invalid. This *may* be caused by a race condition
|
||||
# between connect() and getsockopt(SO_ERROR) (ie. it
|
||||
# finishes connecting in between the two, so there is no
|
||||
# longer an error). However, I'm not sure of that.
|
||||
#
|
||||
# I did get at least one report that the problem went away
|
||||
# when we added this, however.
|
||||
self.connect_to = None
|
||||
elif e.args[0] == errno.EISCONN:
|
||||
# connected successfully (BSD)
|
||||
self.connect_to = None
|
||||
elif e.args[0] in [errno.ECONNREFUSED, errno.ETIMEDOUT,
|
||||
errno.EHOSTUNREACH, errno.ENETUNREACH,
|
||||
errno.EACCES, errno.EPERM]:
|
||||
elif e.args[0] in NET_ERRS + [errno.EACCES, errno.EPERM]:
|
||||
# a "normal" kind of error
|
||||
self.connect_to = None
|
||||
self.seterr(e)
|
||||
|
@ -1,5 +1,15 @@
|
||||
exec >&2
|
||||
redo-ifchange runpython.c
|
||||
gcc -Wall -o $3 runpython.c \
|
||||
ARCHES=""
|
||||
printf "Platforms: "
|
||||
for d in /usr/libexec/gcc/darwin/*; do
|
||||
PLAT=$(basename "$d")
|
||||
[ "$PLAT" != "ppc64" ] || continue # fails for some reason on my Mac
|
||||
ARCHES="$ARCHES -arch $PLAT"
|
||||
printf "$PLAT "
|
||||
done
|
||||
printf "\n"
|
||||
gcc $ARCHES \
|
||||
-Wall -o $3 runpython.c \
|
||||
-I/usr/include/python2.5 \
|
||||
-lpython2.5
|
||||
|
@ -3,6 +3,7 @@ import my
|
||||
|
||||
|
||||
configchange_callback = setconnect_callback = None
|
||||
objc_validator = objc.signature('@@:N^@o^@')
|
||||
|
||||
|
||||
def config_changed():
|
||||
@ -39,7 +40,7 @@ class SshuttleNet(NSObject):
|
||||
def setSubnet_(self, v):
|
||||
self._k_subnet = v
|
||||
config_changed()
|
||||
@objc.accessor
|
||||
@objc_validator
|
||||
def validateSubnet_error_(self, value, error):
|
||||
#print 'validateSubnet!'
|
||||
return True, _validate_ip(value), error
|
||||
@ -49,7 +50,7 @@ class SshuttleNet(NSObject):
|
||||
def setWidth_(self, v):
|
||||
self._k_width = v
|
||||
config_changed()
|
||||
@objc.accessor
|
||||
@objc_validator
|
||||
def validateWidth_error_(self, value, error):
|
||||
#print 'validateWidth!'
|
||||
return True, _validate_width(value), error
|
||||
@ -118,7 +119,7 @@ class SshuttleServer(NSObject):
|
||||
self._k_host = v
|
||||
self.setTitle_(None)
|
||||
config_changed()
|
||||
@objc.accessor
|
||||
@objc_validator
|
||||
def validateHost_error_(self, value, error):
|
||||
#print 'validatehost! %r %r %r' % (self, value, error)
|
||||
while value.startswith('-'):
|
||||
|
Reference in New Issue
Block a user