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:
vieira 2015-12-05 03:40:26 +00:00 committed by Brian May
parent 6e15e69029
commit 4241381d82
11 changed files with 111 additions and 65 deletions

View File

@ -1,6 +1,8 @@
language: python
python:
- 2.6
- 2.7
- 3.4
- 3.5
- pypy

10
conftest.py Normal file
View 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")

View File

@ -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.

View File

@ -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")

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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())

View File

@ -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

View File

@ -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)

View File

@ -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