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
parent 36a1d7ead9
commit df89afe365
5 changed files with 66 additions and 37 deletions

View File

@ -13,8 +13,9 @@ while 1:
content = z.decompress(sys.stdin.read(nbytes)) content = z.decompress(sys.stdin.read(nbytes))
module = imp.new_module(name) module = imp.new_module(name)
parent, _, parent_name = name.rpartition(".") parents = name.rsplit(".", 1)
if parent != "": if len(parents) == 2:
parent, parent_name = parents
setattr(sys.modules[parent], parent_name, module) setattr(sys.modules[parent], parent_name, module)
code = compile(content, name, "exec") code = compile(content, name, "exec")

View File

@ -5,6 +5,16 @@ import errno
logprefix = '' logprefix = ''
verbose = 0 verbose = 0
if sys.version_info[0] == 3:
binary_type = bytes
def b(s):
return s.encode("latin-1")
else:
binary_type = str
def b(s):
return s
def log(s): def log(s):
try: try:
@ -62,7 +72,8 @@ def islocal(ip, family):
try: try:
try: try:
sock.bind((ip, 0)) sock.bind((ip, 0))
except socket.error as e: except socket.error:
_, e = sys.exc_info()[:2]
if e.args[0] == errno.EADDRNOTAVAIL: if e.args[0] == errno.EADDRNOTAVAIL:
return False # not a local IP return False # not a local IP
else: else:

View File

@ -22,7 +22,8 @@ hostnames = {}
queue = {} queue = {}
try: try:
null = open('/dev/null', 'wb') null = open('/dev/null', 'wb')
except IOError as e: except IOError:
_, e = sys.exc_info()[:2]
log('warning: %s\n' % e) log('warning: %s\n' % e)
null = os.popen("sh -c 'while read x; do :; done'", 'wb', 4096) 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()): for name, ip in sorted(hostnames.items()):
f.write('%s,%s\n' % (name, ip)) f.write('%s,%s\n' % (name, ip))
f.close() f.close()
os.chmod(tmpname, 0o600) os.chmod(tmpname, 384) # 600 in octal, 'rw-------'
os.rename(tmpname, CACHEFILE) os.rename(tmpname, CACHEFILE)
finally: finally:
try: try:
@ -50,7 +51,8 @@ def write_host_cache():
def read_host_cache(): def read_host_cache():
try: try:
f = open(CACHEFILE) f = open(CACHEFILE)
except IOError as e: except IOError:
_, e = sys.exc_info()[:2]
if e.errno == errno.ENOENT: if e.errno == errno.ENOENT:
return return
else: else:
@ -124,7 +126,8 @@ def _check_netstat():
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null) p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
content = p.stdout.read() content = p.stdout.read()
p.wait() p.wait()
except OSError as e: except OSError:
_, e = sys.exc_info()[:2]
log('%r failed: %r\n' % (argv, e)) log('%r failed: %r\n' % (argv, e))
return return
@ -144,7 +147,8 @@ def _check_smb(hostname):
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null) p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
lines = p.stdout.readlines() lines = p.stdout.readlines()
p.wait() p.wait()
except OSError as e: except OSError:
_, e = sys.exc_info()[:2]
log('%r failed: %r\n' % (argv, e)) log('%r failed: %r\n' % (argv, e))
_smb_ok = False _smb_ok = False
return return
@ -201,7 +205,8 @@ def _check_nmb(hostname, is_workgroup, is_master):
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null) p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
lines = p.stdout.readlines() lines = p.stdout.readlines()
rv = p.wait() rv = p.wait()
except OSError as e: except OSError:
_, e = sys.exc_info()[:2]
log('%r failed: %r\n' % (argv, e)) log('%r failed: %r\n' % (argv, e))
_nmb_ok = False _nmb_ok = False
return return

View File

@ -147,7 +147,8 @@ class DnsProxy(Handler):
try: try:
sock.send(self.request) sock.send(self.request)
self.socks.append(sock) 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: if e.args[0] in ssnet.NET_ERRS:
# might have been spurious; try again. # might have been spurious; try again.
# Note: these errors sometimes are reported by recv(), # Note: these errors sometimes are reported by recv(),
@ -164,7 +165,8 @@ class DnsProxy(Handler):
try: try:
data = sock.recv(4096) data = sock.recv(4096)
except socket.error as e: except socket.error:
_, e = sys.exc_info()[:2]
self.socks.remove(sock) self.socks.remove(sock)
del self.peers[sock] del self.peers[sock]
@ -199,14 +201,16 @@ class UdpProxy(Handler):
debug2('UDP: sending to %r port %d\n' % dstip) debug2('UDP: sending to %r port %d\n' % dstip)
try: try:
self.sock.sendto(data, dstip) 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)) log('UDP send to %r port %d: %s\n' % (dstip[0], dstip[1], e))
return return
def callback(self, sock): def callback(self, sock):
try: try:
data, peer = sock.recvfrom(4096) 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)) log('UDP recv from %r port %d: %s\n' % (peer[0], peer[1], e))
return return
debug2('UDP response: %d bytes\n' % len(data)) debug2('UDP response: %d bytes\n' % len(data))

View File

@ -1,9 +1,10 @@
import sys
import struct import struct
import socket import socket
import errno import errno
import select import select
import os 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 MAX_CHANNEL = 65535
@ -75,7 +76,8 @@ def _fds(l):
def _nb_clean(func, *args): def _nb_clean(func, *args):
try: try:
return func(*args) return func(*args)
except OSError as e: except OSError:
_, e = sys.exc_info()[:2]
if e.errno not in (errno.EWOULDBLOCK, errno.EAGAIN): if e.errno not in (errno.EWOULDBLOCK, errno.EAGAIN):
raise raise
else: else:
@ -88,7 +90,8 @@ def _try_peername(sock):
pn = sock.getpeername() pn = sock.getpeername()
if pn: if pn:
return '%s:%s' % (pn[0], pn[1]) 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): if e.args[0] not in (errno.ENOTCONN, errno.ENOTSOCK):
raise raise
return 'unknown' return 'unknown'
@ -144,7 +147,8 @@ class SockWrapper:
self.rsock.connect(self.connect_to) self.rsock.connect(self.connect_to)
# connected successfully (Linux) # connected successfully (Linux)
self.connect_to = None 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)) debug3('%r: connect result: %s\n' % (self, e))
if e.args[0] == errno.EINVAL: if e.args[0] == errno.EINVAL:
# this is what happens when you call connect() on a socket # this is what happens when you call connect() on a socket
@ -191,7 +195,8 @@ class SockWrapper:
self.shut_write = True self.shut_write = True
try: try:
self.wsock.shutdown(SHUT_WR) self.wsock.shutdown(SHUT_WR)
except socket.error as e: except socket.error:
_, e = sys.exc_info()[:2]
self.seterr('nowrite: %s' % e) self.seterr('nowrite: %s' % e)
def too_full(self): def too_full(self):
@ -203,7 +208,8 @@ class SockWrapper:
self.wsock.setblocking(False) self.wsock.setblocking(False)
try: try:
return _nb_clean(os.write, self.wsock.fileno(), buf) 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: if e.errno == errno.EPIPE:
debug1('%r: uwrite: got EPIPE\n' % self) debug1('%r: uwrite: got EPIPE\n' % self)
self.nowrite() self.nowrite()
@ -225,9 +231,10 @@ class SockWrapper:
self.rsock.setblocking(False) self.rsock.setblocking(False)
try: try:
return _nb_clean(os.read, self.rsock.fileno(), 65536) 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) 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): def fill(self):
if self.buf: if self.buf:
@ -235,7 +242,7 @@ class SockWrapper:
rb = self.uread() rb = self.uread()
if rb: if rb:
self.buf.append(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() self.noread()
def copy_to(self, outwrap): def copy_to(self, outwrap):
@ -333,11 +340,11 @@ class Mux(Handler):
self.channels = {} self.channels = {}
self.chani = 0 self.chani = 0
self.want = 0 self.want = 0
self.inbuf = b'' self.inbuf = b('')
self.outbuf = [] self.outbuf = []
self.fullness = 0 self.fullness = 0
self.too_full = False self.too_full = False
self.send(0, CMD_PING, b'chicken') self.send(0, CMD_PING, b('chicken'))
def next_channel(self): def next_channel(self):
# channel 0 is special, so we never allocate it # channel 0 is special, so we never allocate it
@ -357,7 +364,7 @@ class Mux(Handler):
def check_fullness(self): def check_fullness(self):
if self.fullness > 32768: if self.fullness > 32768:
if not self.too_full: if not self.too_full:
self.send(0, CMD_PING, b'rttest') self.send(0, CMD_PING, b('rttest'))
self.too_full = True self.too_full = True
# ob = [] # ob = []
# for b in self.outbuf: # for b in self.outbuf:
@ -366,9 +373,9 @@ class Mux(Handler):
# log('outbuf: %d %r\n' % (self.amount_queued(), ob)) # log('outbuf: %d %r\n' % (self.amount_queued(), ob))
def send(self, channel, cmd, data): def send(self, channel, cmd, data):
assert isinstance(data, bytes) assert isinstance(data, binary_type)
assert len(data) <= 65535 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) self.outbuf.append(p)
debug2(' > channel=%d cmd=%s len=%d (fullness=%d)\n' debug2(' > channel=%d cmd=%s len=%d (fullness=%d)\n'
% (channel, cmd_to_name.get(cmd, hex(cmd)), % (channel, cmd_to_name.get(cmd, hex(cmd)),
@ -434,14 +441,15 @@ class Mux(Handler):
def fill(self): def fill(self):
self.rsock.setblocking(False) self.rsock.setblocking(False)
try: try:
b = _nb_clean(os.read, self.rsock.fileno(), 32768) read = _nb_clean(os.read, self.rsock.fileno(), 32768)
except OSError as e: except OSError:
_, e = sys.exc_info()[:2]
raise Fatal('other end: %r' % e) raise Fatal('other end: %r' % e)
# log('<<< %r\n' % b) # log('<<< %r\n' % b)
if b == b'': # EOF if read == b(''): # EOF
self.ok = False self.ok = False
if b: if read:
self.inbuf += b self.inbuf += read
def handle(self): def handle(self):
self.fill() self.fill()
@ -451,8 +459,8 @@ class Mux(Handler):
if len(self.inbuf) >= (self.want or HDR_LEN): if len(self.inbuf) >= (self.want or HDR_LEN):
(s1, s2, channel, cmd, datalen) = \ (s1, s2, channel, cmd, datalen) = \
struct.unpack('!ccHHH', self.inbuf[:HDR_LEN]) struct.unpack('!ccHHH', self.inbuf[:HDR_LEN])
assert(s1 == b'S') assert(s1 == b('S'))
assert(s2 == b'S') assert(s2 == b('S'))
self.want = datalen + HDR_LEN self.want = datalen + HDR_LEN
if self.want and len(self.inbuf) >= self.want: if self.want and len(self.inbuf) >= self.want:
data = self.inbuf[HDR_LEN:self.want] data = self.inbuf[HDR_LEN:self.want]
@ -496,14 +504,14 @@ class MuxWrapper(SockWrapper):
if not self.shut_read: if not self.shut_read:
debug2('%r: done reading\n' % self) debug2('%r: done reading\n' % self)
self.shut_read = True 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() self.maybe_close()
def nowrite(self): def nowrite(self):
if not self.shut_write: if not self.shut_write:
debug2('%r: done writing\n' % self) debug2('%r: done writing\n' % self)
self.shut_write = True 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() self.maybe_close()
def maybe_close(self): def maybe_close(self):
@ -526,7 +534,7 @@ class MuxWrapper(SockWrapper):
def uread(self): def uread(self):
if self.shut_read: if self.shut_read:
return b'' # EOF return b('') # EOF
else: else:
return None # no data available right now return None # no data available right now