diff options
author | vieira <vieira@yubo.be> | 2015-12-05 03:40:26 +0000 |
---|---|---|
committer | Brian May <brian@linuxpenguins.xyz> | 2016-04-03 13:14:02 +1000 |
commit | 4241381d827ca42fd40b6a4bc5a4e2fdde92577b (patch) | |
tree | 1406e21383f796307e98f7b3242d1094972bc253 | |
parent | 6e15e690295ee6d17d1ab64b85f0adebb7e3ac7a (diff) |
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.
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | conftest.py | 10 | ||||
-rw-r--r-- | docs/requirements.rst | 1 | ||||
-rw-r--r-- | sshuttle/assembler.py | 11 | ||||
-rw-r--r-- | sshuttle/helpers.py | 13 | ||||
-rw-r--r-- | sshuttle/hostwatch.py | 17 | ||||
-rw-r--r-- | sshuttle/server.py | 48 | ||||
-rw-r--r-- | sshuttle/ssh.py | 6 | ||||
-rw-r--r-- | sshuttle/ssnet.py | 56 | ||||
-rw-r--r-- | sshuttle/tests/server/test_server.py | 8 | ||||
-rw-r--r-- | tox.ini | 4 |
11 files changed, 111 insertions, 65 deletions
diff --git a/.travis.yml b/.travis.yml index 4ab35a4..28e7a80 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: python python: +- 2.6 - 2.7 +- 3.4 - 3.5 - pypy diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..969c119 --- /dev/null +++ b/conftest.py @@ -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") diff --git a/docs/requirements.rst b/docs/requirements.rst index 0c66538..c35824f 100644 --- a/docs/requirements.rst +++ b/docs/requirements.rst @@ -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. diff --git a/sshuttle/assembler.py b/sshuttle/assembler.py index 330528d..137331e 100644 --- a/sshuttle/assembler.py +++ b/sshuttle/assembler.py @@ -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") diff --git a/sshuttle/helpers.py b/sshuttle/helpers.py index 4e80e33..67a6013 100644 --- a/sshuttle/helpers.py +++ b/sshuttle/helpers.py @@ -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: diff --git a/sshuttle/hostwatch.py b/sshuttle/hostwatch.py index 95a539a..be210f9 100644 --- a/sshuttle/hostwatch.py +++ b/sshuttle/hostwatch.py @@ -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 diff --git a/sshuttle/server.py b/sshuttle/server.py index 78252b8..a47c760 100644 --- a/sshuttle/server.py +++ b/sshuttle/server.py @@ -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) diff --git a/sshuttle/ssh.py b/sshuttle/ssh.py index cc89c0d..1a571d5 100644 --- a/sshuttle/ssh.py +++ b/sshuttle/ssh.py @@ -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()) diff --git a/sshuttle/ssnet.py b/sshuttle/ssnet.py index 218e7b5..fe1de1e 100644 --- a/sshuttle/ssnet.py +++ b/sshuttle/ssnet.py @@ -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 diff --git a/sshuttle/tests/server/test_server.py b/sshuttle/tests/server/test_server.py index bf6985f..6b437f7 100644 --- a/sshuttle/tests/server/test_server.py +++ b/sshuttle/tests/server/test_server.py @@ -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) @@ -1,12 +1,16 @@ [tox] downloadcache = {toxworkdir}/cache/ envlist = + py26, py27, + py34, py35, [testenv] basepython = + py26: python2.6 py27: python2.7 + py34: python3.4 py35: python3.5 commands = py.test |