mirror of
https://github.com/sshuttle/sshuttle.git
synced 2024-11-21 23:43:18 +01:00
Backward compatibility with Python 2.4 (server)
It is often the case that the user has no administrative control over the server that is being used. As such it is important to support as many versions as possible, at least on the remote server end. These fixes will allow sshuttle to be used with servers that have only python 2.4 or python 2.6 installed while hopefully not breaking the compatibility with 2.7 and 3.5.
This commit is contained in:
parent
6e15e69029
commit
4241381d82
@ -1,6 +1,8 @@
|
||||
language: python
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.4
|
||||
- 3.5
|
||||
- pypy
|
||||
|
||||
|
10
conftest.py
Normal file
10
conftest.py
Normal file
@ -0,0 +1,10 @@
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
good_python = sys.version_info >= (3, 5)
|
||||
else:
|
||||
good_python = sys.version_info >= (2, 7)
|
||||
|
||||
collect_ignore = []
|
||||
if not good_python:
|
||||
collect_ignore.append("sshuttle/tests/client")
|
@ -62,6 +62,7 @@ cmd.exe with Administrator access. See :doc:`windows` for more information.
|
||||
|
||||
Server side Requirements
|
||||
------------------------
|
||||
Server requirements are more relaxed, however it is recommended that you use
|
||||
Python 2.7 or Python 3.5.
|
||||
|
||||
|
||||
|
@ -4,19 +4,20 @@ import imp
|
||||
|
||||
z = zlib.decompressobj()
|
||||
while 1:
|
||||
name = stdin.readline().strip()
|
||||
name = sys.stdin.readline().strip()
|
||||
if name:
|
||||
name = name.decode("ASCII")
|
||||
|
||||
nbytes = int(stdin.readline())
|
||||
nbytes = int(sys.stdin.readline())
|
||||
if verbosity >= 2:
|
||||
sys.stderr.write('server: assembling %r (%d bytes)\n'
|
||||
% (name, nbytes))
|
||||
content = z.decompress(stdin.read(nbytes))
|
||||
content = z.decompress(sys.stdin.read(nbytes))
|
||||
|
||||
module = imp.new_module(name)
|
||||
parent, _, parent_name = name.rpartition(".")
|
||||
if parent != "":
|
||||
parents = name.rsplit(".", 1)
|
||||
if len(parents) == 2:
|
||||
parent, parent_name = parents
|
||||
setattr(sys.modules[parent], parent_name, module)
|
||||
|
||||
code = compile(content, name, "exec")
|
||||
|
@ -5,6 +5,16 @@ import errno
|
||||
logprefix = ''
|
||||
verbose = 0
|
||||
|
||||
if sys.version_info[0] == 3:
|
||||
binary_type = bytes
|
||||
|
||||
def b(s):
|
||||
return s.encode("ASCII")
|
||||
else:
|
||||
binary_type = str
|
||||
|
||||
def b(s):
|
||||
return s
|
||||
|
||||
def log(s):
|
||||
global logprefix
|
||||
@ -70,7 +80,8 @@ def islocal(ip, family):
|
||||
try:
|
||||
try:
|
||||
sock.bind((ip, 0))
|
||||
except socket.error as e:
|
||||
except socket.error:
|
||||
_, e = sys.exc_info()[:2]
|
||||
if e.args[0] == errno.EADDRNOTAVAIL:
|
||||
return False # not a local IP
|
||||
else:
|
||||
|
@ -22,7 +22,8 @@ hostnames = {}
|
||||
queue = {}
|
||||
try:
|
||||
null = open('/dev/null', 'wb')
|
||||
except IOError as e:
|
||||
except IOError:
|
||||
_, e = sys.exc_info()[:2]
|
||||
log('warning: %s\n' % e)
|
||||
null = os.popen("sh -c 'while read x; do :; done'", 'wb', 4096)
|
||||
|
||||
@ -38,7 +39,7 @@ def write_host_cache():
|
||||
for name, ip in sorted(hostnames.items()):
|
||||
f.write(('%s,%s\n' % (name, ip)).encode("ASCII"))
|
||||
f.close()
|
||||
os.chmod(tmpname, 0o600)
|
||||
os.chmod(tmpname, 384) # 600 in octal, 'rw-------'
|
||||
os.rename(tmpname, CACHEFILE)
|
||||
finally:
|
||||
try:
|
||||
@ -50,7 +51,8 @@ def write_host_cache():
|
||||
def read_host_cache():
|
||||
try:
|
||||
f = open(CACHEFILE)
|
||||
except IOError as e:
|
||||
except IOError:
|
||||
_, e = sys.exc_info()[:2]
|
||||
if e.errno == errno.ENOENT:
|
||||
return
|
||||
else:
|
||||
@ -124,7 +126,8 @@ def _check_netstat():
|
||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
|
||||
content = p.stdout.read().decode("ASCII")
|
||||
p.wait()
|
||||
except OSError as e:
|
||||
except OSError:
|
||||
_, e = sys.exc_info()[:2]
|
||||
log('%r failed: %r\n' % (argv, e))
|
||||
return
|
||||
|
||||
@ -144,7 +147,8 @@ def _check_smb(hostname):
|
||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
|
||||
lines = p.stdout.readlines()
|
||||
p.wait()
|
||||
except OSError as e:
|
||||
except OSError:
|
||||
_, e = sys.exc_info()[:2]
|
||||
log('%r failed: %r\n' % (argv, e))
|
||||
_smb_ok = False
|
||||
return
|
||||
@ -201,7 +205,8 @@ def _check_nmb(hostname, is_workgroup, is_master):
|
||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
|
||||
lines = p.stdout.readlines()
|
||||
rv = p.wait()
|
||||
except OSError as e:
|
||||
except OSError:
|
||||
_, e = sys.exc_info()[:2]
|
||||
log('%r failed: %r\n' % (argv, e))
|
||||
_nmb_ok = False
|
||||
return
|
||||
|
@ -12,29 +12,29 @@ import sshuttle.helpers as helpers
|
||||
import sshuttle.hostwatch as hostwatch
|
||||
import subprocess as ssubprocess
|
||||
from sshuttle.ssnet import Handler, Proxy, Mux, MuxWrapper
|
||||
from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, \
|
||||
from sshuttle.helpers import b, log, debug1, debug2, debug3, Fatal, \
|
||||
resolvconf_random_nameserver
|
||||
|
||||
|
||||
def _ipmatch(ipstr):
|
||||
# FIXME: IPv4 only
|
||||
if ipstr == b'default':
|
||||
ipstr = b'0.0.0.0/0'
|
||||
m = re.match(b'^(\d+(\.\d+(\.\d+(\.\d+)?)?)?)(?:/(\d+))?$', ipstr)
|
||||
if ipstr == 'default':
|
||||
ipstr = '0.0.0.0/0'
|
||||
m = re.match('^(\d+(\.\d+(\.\d+(\.\d+)?)?)?)(?:/(\d+))?$', ipstr)
|
||||
if m:
|
||||
g = m.groups()
|
||||
ips = g[0]
|
||||
width = int(g[4] or 32)
|
||||
if g[1] is None:
|
||||
ips += b'.0.0.0'
|
||||
ips += '.0.0.0'
|
||||
width = min(width, 8)
|
||||
elif g[2] is None:
|
||||
ips += b'.0.0'
|
||||
ips += '.0.0'
|
||||
width = min(width, 16)
|
||||
elif g[3] is None:
|
||||
ips += b'.0'
|
||||
ips += '.0'
|
||||
width = min(width, 24)
|
||||
ips = ips.decode("ASCII")
|
||||
ips = ips
|
||||
return (struct.unpack('!I', socket.inet_aton(ips))[0], width)
|
||||
|
||||
|
||||
@ -66,7 +66,7 @@ def _list_routes():
|
||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
|
||||
routes = []
|
||||
for line in p.stdout:
|
||||
cols = re.split(b'\s+', line)
|
||||
cols = re.split(r'\s+', line.decode("ASCII"))
|
||||
ipw = _ipmatch(cols[0])
|
||||
if not ipw:
|
||||
continue # some lines won't be parseable; never mind
|
||||
@ -152,7 +152,8 @@ class DnsProxy(Handler):
|
||||
try:
|
||||
sock.send(self.request)
|
||||
self.socks.append(sock)
|
||||
except socket.error as e:
|
||||
except socket.error:
|
||||
_, e = sys.exc_info()[:2]
|
||||
if e.args[0] in ssnet.NET_ERRS:
|
||||
# might have been spurious; try again.
|
||||
# Note: these errors sometimes are reported by recv(),
|
||||
@ -169,7 +170,8 @@ class DnsProxy(Handler):
|
||||
|
||||
try:
|
||||
data = sock.recv(4096)
|
||||
except socket.error as e:
|
||||
except socket.error:
|
||||
_, e = sys.exc_info()[:2]
|
||||
self.socks.remove(sock)
|
||||
del self.peers[sock]
|
||||
|
||||
@ -204,14 +206,16 @@ class UdpProxy(Handler):
|
||||
debug2('UDP: sending to %r port %d\n' % dstip)
|
||||
try:
|
||||
self.sock.sendto(data, dstip)
|
||||
except socket.error as e:
|
||||
except socket.error:
|
||||
_, e = sys.exc_info()[:2]
|
||||
log('UDP send to %r port %d: %s\n' % (dstip[0], dstip[1], e))
|
||||
return
|
||||
|
||||
def callback(self, sock):
|
||||
try:
|
||||
data, peer = sock.recvfrom(4096)
|
||||
except socket.error as e:
|
||||
except socket.error:
|
||||
_, e = sys.exc_info()[:2]
|
||||
log('UDP recv from %r port %d: %s\n' % (peer[0], peer[1], e))
|
||||
return
|
||||
debug2('UDP response: %d bytes\n' % len(data))
|
||||
@ -244,26 +248,26 @@ def main(latency_control):
|
||||
socket.fromfd(sys.stdout.fileno(),
|
||||
socket.AF_INET, socket.SOCK_STREAM))
|
||||
handlers.append(mux)
|
||||
routepkt = b''
|
||||
routepkt = ''
|
||||
for r in routes:
|
||||
routepkt += b'%d,%s,%d\n' % (r[0], r[1].encode("ASCII"), r[2])
|
||||
mux.send(0, ssnet.CMD_ROUTES, routepkt)
|
||||
routepkt += '%d,%s,%d\n' % r
|
||||
mux.send(0, ssnet.CMD_ROUTES, b(routepkt))
|
||||
|
||||
hw = Hostwatch()
|
||||
hw.leftover = b''
|
||||
hw.leftover = b('')
|
||||
|
||||
def hostwatch_ready(sock):
|
||||
assert(hw.pid)
|
||||
content = hw.sock.recv(4096)
|
||||
if content:
|
||||
lines = (hw.leftover + content).split(b'\n')
|
||||
lines = (hw.leftover + content).split(b('\n'))
|
||||
if lines[-1]:
|
||||
# no terminating newline: entry isn't complete yet!
|
||||
hw.leftover = lines.pop()
|
||||
lines.append(b'')
|
||||
lines.append(b(''))
|
||||
else:
|
||||
hw.leftover = b''
|
||||
mux.send(0, ssnet.CMD_HOST_LIST, b'\n'.join(lines))
|
||||
hw.leftover = b('')
|
||||
mux.send(0, ssnet.CMD_HOST_LIST, b('\n').join(lines))
|
||||
else:
|
||||
raise Fatal('hostwatch process died')
|
||||
|
||||
@ -275,7 +279,7 @@ def main(latency_control):
|
||||
mux.got_host_req = got_host_req
|
||||
|
||||
def new_channel(channel, data):
|
||||
(family, dstip, dstport) = data.split(b',', 2)
|
||||
(family, dstip, dstport) = data.decode("ASCII").split(',', 2)
|
||||
family = int(family)
|
||||
dstport = int(dstport)
|
||||
outwrap = ssnet.connect_dst(family, dstip, dstport)
|
||||
|
@ -95,10 +95,10 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
|
||||
b"\n")
|
||||
|
||||
pyscript = r"""
|
||||
import sys;
|
||||
import sys, os;
|
||||
verbosity=%d;
|
||||
stdin=getattr(sys.stdin,"buffer",sys.stdin);
|
||||
exec(compile(stdin.read(%d), "assembler.py", "exec"))
|
||||
sys.stdin = os.fdopen(0, "rb");
|
||||
exec(compile(sys.stdin.read(%d), "assembler.py", "exec"))
|
||||
""" % (helpers.verbose or 0, len(content))
|
||||
pyscript = re.sub(r'\s+', ' ', pyscript.strip())
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import sys
|
||||
import struct
|
||||
import socket
|
||||
import errno
|
||||
import select
|
||||
import os
|
||||
from sshuttle.helpers import log, debug1, debug2, debug3, Fatal
|
||||
from sshuttle.helpers import b, binary_type, log, debug1, debug2, debug3, Fatal
|
||||
|
||||
MAX_CHANNEL = 65535
|
||||
|
||||
@ -75,7 +76,8 @@ def _fds(l):
|
||||
def _nb_clean(func, *args):
|
||||
try:
|
||||
return func(*args)
|
||||
except OSError as e:
|
||||
except OSError:
|
||||
_, e = sys.exc_info()[:2]
|
||||
if e.errno not in (errno.EWOULDBLOCK, errno.EAGAIN):
|
||||
raise
|
||||
else:
|
||||
@ -88,7 +90,8 @@ def _try_peername(sock):
|
||||
pn = sock.getpeername()
|
||||
if pn:
|
||||
return '%s:%s' % (pn[0], pn[1])
|
||||
except socket.error as e:
|
||||
except socket.error:
|
||||
_, e = sys.exc_info()[:2]
|
||||
if e.args[0] not in (errno.ENOTCONN, errno.ENOTSOCK):
|
||||
raise
|
||||
return 'unknown'
|
||||
@ -144,7 +147,8 @@ class SockWrapper:
|
||||
self.rsock.connect(self.connect_to)
|
||||
# connected successfully (Linux)
|
||||
self.connect_to = None
|
||||
except socket.error as e:
|
||||
except socket.error:
|
||||
_, e = sys.exc_info()[:2]
|
||||
debug3('%r: connect result: %s\n' % (self, e))
|
||||
if e.args[0] == errno.EINVAL:
|
||||
# this is what happens when you call connect() on a socket
|
||||
@ -191,7 +195,8 @@ class SockWrapper:
|
||||
self.shut_write = True
|
||||
try:
|
||||
self.wsock.shutdown(SHUT_WR)
|
||||
except socket.error as e:
|
||||
except socket.error:
|
||||
_, e = sys.exc_info()[:2]
|
||||
self.seterr('nowrite: %s' % e)
|
||||
|
||||
def too_full(self):
|
||||
@ -203,7 +208,8 @@ class SockWrapper:
|
||||
self.wsock.setblocking(False)
|
||||
try:
|
||||
return _nb_clean(os.write, self.wsock.fileno(), buf)
|
||||
except OSError as e:
|
||||
except OSError:
|
||||
_, e = sys.exc_info()[:2]
|
||||
if e.errno == errno.EPIPE:
|
||||
debug1('%r: uwrite: got EPIPE\n' % self)
|
||||
self.nowrite()
|
||||
@ -225,9 +231,10 @@ class SockWrapper:
|
||||
self.rsock.setblocking(False)
|
||||
try:
|
||||
return _nb_clean(os.read, self.rsock.fileno(), 65536)
|
||||
except OSError as e:
|
||||
except OSError:
|
||||
_, e = sys.exc_info()[:2]
|
||||
self.seterr('uread: %s' % e)
|
||||
return b'' # unexpected error... we'll call it EOF
|
||||
return b('') # unexpected error... we'll call it EOF
|
||||
|
||||
def fill(self):
|
||||
if self.buf:
|
||||
@ -235,7 +242,7 @@ class SockWrapper:
|
||||
rb = self.uread()
|
||||
if rb:
|
||||
self.buf.append(rb)
|
||||
if rb == b'': # empty string means EOF; None means temporarily empty
|
||||
if rb == b(''): # empty string means EOF; None means temporarily empty
|
||||
self.noread()
|
||||
|
||||
def copy_to(self, outwrap):
|
||||
@ -333,11 +340,11 @@ class Mux(Handler):
|
||||
self.channels = {}
|
||||
self.chani = 0
|
||||
self.want = 0
|
||||
self.inbuf = b''
|
||||
self.inbuf = b('')
|
||||
self.outbuf = []
|
||||
self.fullness = 0
|
||||
self.too_full = False
|
||||
self.send(0, CMD_PING, b'chicken')
|
||||
self.send(0, CMD_PING, b('chicken'))
|
||||
|
||||
def next_channel(self):
|
||||
# channel 0 is special, so we never allocate it
|
||||
@ -357,7 +364,7 @@ class Mux(Handler):
|
||||
def check_fullness(self):
|
||||
if self.fullness > 32768:
|
||||
if not self.too_full:
|
||||
self.send(0, CMD_PING, b'rttest')
|
||||
self.send(0, CMD_PING, b('rttest'))
|
||||
self.too_full = True
|
||||
# ob = []
|
||||
# for b in self.outbuf:
|
||||
@ -366,9 +373,9 @@ class Mux(Handler):
|
||||
# log('outbuf: %d %r\n' % (self.amount_queued(), ob))
|
||||
|
||||
def send(self, channel, cmd, data):
|
||||
assert isinstance(data, bytes)
|
||||
assert isinstance(data, binary_type)
|
||||
assert len(data) <= 65535
|
||||
p = struct.pack('!ccHHH', b'S', b'S', channel, cmd, len(data)) + data
|
||||
p = struct.pack('!ccHHH', b('S'), b('S'), channel, cmd, len(data)) + data
|
||||
self.outbuf.append(p)
|
||||
debug2(' > channel=%d cmd=%s len=%d (fullness=%d)\n'
|
||||
% (channel, cmd_to_name.get(cmd, hex(cmd)),
|
||||
@ -434,14 +441,15 @@ class Mux(Handler):
|
||||
def fill(self):
|
||||
self.rsock.setblocking(False)
|
||||
try:
|
||||
b = _nb_clean(os.read, self.rsock.fileno(), 32768)
|
||||
except OSError as e:
|
||||
read = _nb_clean(os.read, self.rsock.fileno(), 32768)
|
||||
except OSError:
|
||||
_, e = sys.exc_info()[:2]
|
||||
raise Fatal('other end: %r' % e)
|
||||
# log('<<< %r\n' % b)
|
||||
if b == b'': # EOF
|
||||
if read == b(''): # EOF
|
||||
self.ok = False
|
||||
if b:
|
||||
self.inbuf += b
|
||||
if read:
|
||||
self.inbuf += read
|
||||
|
||||
def handle(self):
|
||||
self.fill()
|
||||
@ -451,8 +459,8 @@ class Mux(Handler):
|
||||
if len(self.inbuf) >= (self.want or HDR_LEN):
|
||||
(s1, s2, channel, cmd, datalen) = \
|
||||
struct.unpack('!ccHHH', self.inbuf[:HDR_LEN])
|
||||
assert(s1 == b'S')
|
||||
assert(s2 == b'S')
|
||||
assert(s1 == b('S'))
|
||||
assert(s2 == b('S'))
|
||||
self.want = datalen + HDR_LEN
|
||||
if self.want and len(self.inbuf) >= self.want:
|
||||
data = self.inbuf[HDR_LEN:self.want]
|
||||
@ -496,14 +504,14 @@ class MuxWrapper(SockWrapper):
|
||||
if not self.shut_read:
|
||||
debug2('%r: done reading\n' % self)
|
||||
self.shut_read = True
|
||||
self.mux.send(self.channel, CMD_TCP_STOP_SENDING, b'')
|
||||
self.mux.send(self.channel, CMD_TCP_STOP_SENDING, b(''))
|
||||
self.maybe_close()
|
||||
|
||||
def nowrite(self):
|
||||
if not self.shut_write:
|
||||
debug2('%r: done writing\n' % self)
|
||||
self.shut_write = True
|
||||
self.mux.send(self.channel, CMD_TCP_EOF, b'')
|
||||
self.mux.send(self.channel, CMD_TCP_EOF, b(''))
|
||||
self.maybe_close()
|
||||
|
||||
def maybe_close(self):
|
||||
@ -526,7 +534,7 @@ class MuxWrapper(SockWrapper):
|
||||
|
||||
def uread(self):
|
||||
if self.shut_read:
|
||||
return b'' # EOF
|
||||
return b('') # EOF
|
||||
else:
|
||||
return None # no data available right now
|
||||
|
||||
|
@ -5,9 +5,9 @@ from mock import patch, Mock, call
|
||||
|
||||
|
||||
def test__ipmatch():
|
||||
assert sshuttle.server._ipmatch(b"1.2.3.4") is not None
|
||||
assert sshuttle.server._ipmatch(b"::1") is None # ipv6 not supported
|
||||
assert sshuttle.server._ipmatch(b"42 Example Street, Melbourne") is None
|
||||
assert sshuttle.server._ipmatch("1.2.3.4") is not None
|
||||
assert sshuttle.server._ipmatch("::1") is None # ipv6 not supported
|
||||
assert sshuttle.server._ipmatch("42 Example Street, Melbourne") is None
|
||||
|
||||
|
||||
def test__ipstr():
|
||||
@ -16,7 +16,7 @@ def test__ipstr():
|
||||
|
||||
|
||||
def test__maskbits():
|
||||
netmask = sshuttle.server._ipmatch(b"255.255.255.0")
|
||||
netmask = sshuttle.server._ipmatch("255.255.255.0")
|
||||
sshuttle.server._maskbits(netmask)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user