Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alter codification for utf-8 in file server.py line 69 to solve issue #93 #41

Open
wants to merge 92 commits into
base: master
Choose a base branch
from

Conversation

maronemoraes
Copy link

import re
import struct
import socket
import traceback
import time
import sys
import os
import platform

import sshuttle.ssnet as ssnet
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 b, log, debug1, debug2, debug3, Fatal,
resolvconf_random_nameserver

def _ipmatch(ipstr):
# FIXME: IPv4 only
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 += '.0.0.0'
width = min(width, 8)
elif g[2] is None:
ips += '.0.0'
width = min(width, 16)
elif g[3] is None:
ips += '.0'
width = min(width, 24)
ips = ips
return (struct.unpack('!I', socket.inet_aton(ips))[0], width)

def _ipstr(ip, width):
# FIXME: IPv4 only
if width >= 32:
return ip
else:
return "%s/%d" % (ip, width)

def _maskbits(netmask):
# FIXME: IPv4 only
if not netmask:
return 32
for i in range(32):
if netmask[0] & _shl(1, i):
return 32 - i
return 0

def _shl(n, bits):
return n * int(2 ** bits)

def _list_routes():
# FIXME: IPv4 only
argv = ['netstat', '-rn']
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
routes = []
for line in p.stdout:
cols = re.split(r'\s+', line.decode("utf-8"))
ipw = _ipmatch(cols[0])
if not ipw:
continue # some lines won't be parseable; never mind
maskw = _ipmatch(cols[2]) # linux only
mask = _maskbits(maskw) # returns 32 if maskw is null
width = min(ipw[1], mask)
ip = ipw[0] & _shl(_shl(1, width) - 1, 32 - width)
routes.append(
(socket.AF_INET, socket.inet_ntoa(struct.pack('!I', ip)), width))
rv = p.wait()
if rv != 0:
log('WARNING: %r returned %d\n' % (argv, rv))
log('WARNING: That prevents --auto-nets from working.\n')
return routes

def list_routes():
for (family, ip, width) in _list_routes():
if not ip.startswith('0.') and not ip.startswith('127.'):
yield (family, ip, width)

def _exc_dump():
exc_info = sys.exc_info()
return ''.join(traceback.format_exception(*exc_info))

def start_hostwatch(seed_hosts):
s1, s2 = socket.socketpair()
pid = os.fork()
if not pid:
# child
rv = 99
try:
try:
s2.close()
os.dup2(s1.fileno(), 1)
os.dup2(s1.fileno(), 0)
s1.close()
rv = hostwatch.hw_main(seed_hosts) or 0
except Exception:
log('%s\n' % _exc_dump())
rv = 98
finally:
os._exit(rv)
s1.close()
return pid, s2

class Hostwatch:

def __init__(self):
    self.pid = 0
    self.sock = None

class DnsProxy(Handler):

def __init__(self, mux, chan, request):
    Handler.__init__(self, [])
    self.timeout = time.time() + 30
    self.mux = mux
    self.chan = chan
    self.tries = 0
    self.request = request
    self.peers = {}
    self.try_send()

def try_send(self):
    if self.tries >= 3:
        return
    self.tries += 1

    family, peer = resolvconf_random_nameserver()

    sock = socket.socket(family, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
    sock.connect((peer, 53))

    self.peers[sock] = peer

    debug2('DNS: sending to %r (try %d)\n' % (peer, self.tries))
    try:
        sock.send(self.request)
        self.socks.append(sock)
    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(),
            # and sometimes by send().  We have to catch both.
            debug2('DNS send to %r: %s\n' % (peer, e))
            self.try_send()
            return
        else:
            log('DNS send to %r: %s\n' % (peer, e))
            return

def callback(self, sock):
    peer = self.peers[sock]

    try:
        data = sock.recv(4096)
    except socket.error:
        _, e = sys.exc_info()[:2]
        self.socks.remove(sock)
        del self.peers[sock]

        if e.args[0] in ssnet.NET_ERRS:
            # might have been spurious; try again.
            # Note: these errors sometimes are reported by recv(),
            # and sometimes by send().  We have to catch both.
            debug2('DNS recv from %r: %s\n' % (peer, e))
            self.try_send()
            return
        else:
            log('DNS recv from %r: %s\n' % (peer, e))
            return
    debug2('DNS response: %d bytes\n' % len(data))
    self.mux.send(self.chan, ssnet.CMD_DNS_RESPONSE, data)
    self.ok = False

class UdpProxy(Handler):

def __init__(self, mux, chan, family):
    sock = socket.socket(family, socket.SOCK_DGRAM)
    Handler.__init__(self, [sock])
    self.timeout = time.time() + 30
    self.mux = mux
    self.chan = chan
    self.sock = sock
    if family == socket.AF_INET:
        self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)

def send(self, dstip, data):
    debug2('UDP: sending to %r port %d\n' % dstip)
    try:
        self.sock.sendto(data, dstip)
    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:
        _, 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))
    hdr = "%s,%r," % (peer[0], peer[1])
    self.mux.send(self.chan, ssnet.CMD_UDP_DATA, hdr + data)

def main(latency_control):
debug1('Starting server with Python version %s\n'
% platform.python_version())

if helpers.verbose >= 1:
    helpers.logprefix = ' s: '
else:
    helpers.logprefix = 'server: '
debug1('latency control setting = %r\n' % latency_control)

routes = list(list_routes())
debug1('available routes:\n')
for r in routes:
    debug1('  %d/%s/%d\n' % r)

# synchronization header
sys.stdout.write('\0\0SSHUTTLE0001')
sys.stdout.flush()

handlers = []
mux = Mux(socket.fromfd(sys.stdin.fileno(),
                        socket.AF_INET, socket.SOCK_STREAM),
          socket.fromfd(sys.stdout.fileno(),
                        socket.AF_INET, socket.SOCK_STREAM))
handlers.append(mux)
routepkt = ''
for r in routes:
    routepkt += '%d,%s,%d\n' % r
mux.send(0, ssnet.CMD_ROUTES, b(routepkt))

hw = Hostwatch()
hw.leftover = b('')

def hostwatch_ready(sock):
    assert(hw.pid)
    content = hw.sock.recv(4096)
    if content:
        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(''))
        else:
            hw.leftover = b('')
        mux.send(0, ssnet.CMD_HOST_LIST, b('\n').join(lines))
    else:
        raise Fatal('hostwatch process died')

def got_host_req(data):
    if not hw.pid:
        (hw.pid, hw.sock) = start_hostwatch(data.strip().split())
        handlers.append(Handler(socks=[hw.sock],
                                callback=hostwatch_ready))
mux.got_host_req = got_host_req

def new_channel(channel, data):
    (family, dstip, dstport) = data.decode("ASCII").split(',', 2)
    family = int(family)
    dstport = int(dstport)
    outwrap = ssnet.connect_dst(family, dstip, dstport)
    handlers.append(Proxy(MuxWrapper(mux, channel), outwrap))
mux.new_channel = new_channel

dnshandlers = {}

def dns_req(channel, data):
    debug2('Incoming DNS request channel=%d.\n' % channel)
    h = DnsProxy(mux, channel, data)
    handlers.append(h)
    dnshandlers[channel] = h
mux.got_dns_req = dns_req

udphandlers = {}

def udp_req(channel, cmd, data):
    debug2('Incoming UDP request channel=%d, cmd=%d\n' % (channel, cmd))
    if cmd == ssnet.CMD_UDP_DATA:
        (dstip, dstport, data) = data.split(",", 2)
        dstport = int(dstport)
        debug2('is incoming UDP data. %r %d.\n' % (dstip, dstport))
        h = udphandlers[channel]
        h.send((dstip, dstport), data)
    elif cmd == ssnet.CMD_UDP_CLOSE:
        debug2('is incoming UDP close\n')
        h = udphandlers[channel]
        h.ok = False
        del mux.channels[channel]

def udp_open(channel, data):
    debug2('Incoming UDP open.\n')
    family = int(data)
    mux.channels[channel] = lambda cmd, data: udp_req(channel, cmd, data)
    if channel in udphandlers:
        raise Fatal('UDP connection channel %d already open' % channel)
    else:
        h = UdpProxy(mux, channel, family)
        handlers.append(h)
        udphandlers[channel] = h
mux.got_udp_open = udp_open

while mux.ok:
    if hw.pid:
        assert(hw.pid > 0)
        (rpid, rv) = os.waitpid(hw.pid, os.WNOHANG)
        if rpid:
            raise Fatal(
                'hostwatch exited unexpectedly: code 0x%04x\n' % rv)

    ssnet.runonce(handlers, mux)
    if latency_control:
        mux.check_fullness()

    if dnshandlers:
        now = time.time()
        remove = []
        for channel, h in dnshandlers.items():
            if h.timeout < now or not h.ok:
                debug3('expiring dnsreqs channel=%d\n' % channel)
                remove.append(channel)
                h.ok = False
        for channel in remove:
            del dnshandlers[channel]
    if udphandlers:
        remove = []
        for channel, h in udphandlers.items():
            if not h.ok:
                debug3('expiring UDP channel=%d\n' % channel)
                remove.append(channel)
                h.ok = False
        for channel in remove:
            del udphandlers[channel]

brianmay and others added 30 commits May 12, 2011 15:23
brianmay and others added 29 commits June 9, 2011 16:30
Use Exception instead of Fatal for errors that should never happen.
There was a problem where trying to bind .v4 and .v6 listeners would set them
to None if there was nothing to bind (if, say, you weren't binding an IPv6
listener).  Later, the code then would try to call a member function of the
listener.  The member function would not do anything if there was no listener,
but trying to dereference None yielded the broken behavior.
@maronemoraes maronemoraes changed the title Alter codification for utf-8 in file server.py line 69 Alter codification for utf-8 in file server.py line 69 to solve issue #93 Apr 23, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants