summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorvieira <vieira@yubo.be>2015-12-05 03:40:26 +0000
committerBrian May <brian@linuxpenguins.xyz>2016-04-03 13:14:02 +1000
commit4241381d827ca42fd40b6a4bc5a4e2fdde92577b (patch)
tree1406e21383f796307e98f7b3242d1094972bc253
parent6e15e690295ee6d17d1ab64b85f0adebb7e3ac7a (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.yml2
-rw-r--r--conftest.py10
-rw-r--r--docs/requirements.rst1
-rw-r--r--sshuttle/assembler.py11
-rw-r--r--sshuttle/helpers.py13
-rw-r--r--sshuttle/hostwatch.py17
-rw-r--r--sshuttle/server.py48
-rw-r--r--sshuttle/ssh.py6
-rw-r--r--sshuttle/ssnet.py56
-rw-r--r--sshuttle/tests/server/test_server.py8
-rw-r--r--tox.ini4
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)
diff --git a/tox.ini b/tox.ini
index e2e93f8..2b66655 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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