Exemple #1
0
def _check_nmb(hostname, is_workgroup, is_master):
    return
    global _nmb_ok
    if not _nmb_ok:
        return
    argv = ['nmblookup'] + ['-M'] * is_master + ['--', hostname]
    debug2(' > n%d%d: %s\n' % (is_workgroup, is_master, hostname))
    try:
        p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
        lines = p.stdout.readlines()
        rv = p.wait()
    except OSError:
        _, e = sys.exc_info()[:2]
        log('%r failed: %r\n' % (argv, e))
        _nmb_ok = False
        return
    if rv:
        log('%r returned %d\n' % (argv, rv))
        return
    for line in lines:
        m = re.match(r'(\d+\.\d+\.\d+\.\d+) (\w+)<\w\w>\n', line)
        if m:
            g = m.groups()
            (ip, name) = (g[0], g[1].lower())
            debug3('<    %s -> %s\n' % (name, ip))
            if is_workgroup:
                _enqueue(_check_smb, ip)
            else:
                found_host(name, ip)
                check_host(name)
Exemple #2
0
def onaccept_tcp(listener, method, mux, handlers):
    global _extra_fd
    try:
        sock, srcip = listener.accept()
    except socket.error as e:
        if e.args[0] in [errno.EMFILE, errno.ENFILE]:
            debug1('Rejected incoming connection: too many open files!\n')
            # free up an fd so we can eat the connection
            os.close(_extra_fd)
            try:
                sock, srcip = listener.accept()
                sock.close()
            finally:
                _extra_fd = os.open('/dev/null', os.O_RDONLY)
            return
        else:
            raise

    dstip = method.get_tcp_dstip(sock)
    debug1('Accept TCP: %s:%r -> %s:%r.\n' % (srcip[0], srcip[1],
                                              dstip[0], dstip[1]))
    if dstip[1] == sock.getsockname()[1] and islocal(dstip[0], sock.family):
        debug1("-- ignored: that's my address!\n")
        sock.close()
        return
    chan = mux.next_channel()
    if not chan:
        log('warning: too many open channels.  Discarded connection.\n')
        sock.close()
        return
    mux.send(chan, ssnet.CMD_TCP_CONNECT, b'%d,%s,%d' %
             (sock.family, dstip[0].encode("ASCII"), dstip[1]))
    outwrap = MuxWrapper(mux, chan)
    handlers.append(Proxy(SockWrapper(sock, sock), outwrap))
    expire_connections(time.time(), mux)
Exemple #3
0
    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
Exemple #4
0
def _list_routes():
    # FIXME: IPv4 only
    argv = ['netstat', '-rn']
    env = {
        'PATH': os.environ['PATH'],
        'LC_ALL': "C",
    }
    p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
    routes = []
    for line in p.stdout:
        cols = re.split(r'\s+', line.decode("ASCII"))
        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
Exemple #5
0
 def send(self, dstip, data):
     debug2('UDP: sending to %r port %d\n' % dstip)
     try:
         self.sock.sendto(data, dstip)
     except socket.error as e:
         log('UDP send to %r port %d: %s\n' % (dstip[0], dstip[1], e))
         return
Exemple #6
0
    def __init__(self, method_name, sudo_pythonpath, ttl):
        self.auto_nets = []

        argvbase = ([sys.executable, sys.argv[0]] +
                    ['-v'] * (helpers.verbose or 0) +
                    ['--method', method_name] +
                    ['--firewall'])
        if ssyslog._p:
            argvbase += ['--syslog']

        # Determine how to prefix the command in order to elevate privileges.
        if platform.platform().startswith('OpenBSD'):
            elev_prefix = ['doas']  # OpenBSD uses built in `doas`
        else:
            elev_prefix = ['sudo', '-p', '[local sudo] Password: '******'/usr/bin/env',
                            'PYTHONPATH=%s' %
                            os.path.dirname(os.path.dirname(__file__))]
        argv_tries = [elev_prefix + argvbase, argvbase]

        # we can't use stdin/stdout=subprocess.PIPE here, as we normally would,
        # because stupid Linux 'su' requires that stdin be attached to a tty.
        # Instead, attach a *bidirectional* socket to its stdout, and use
        # that for talking in both directions.
        (s1, s2) = socket.socketpair()

        def setup():
            # run in the child process
            s2.close()
        if os.getuid() == 0:
            argv_tries = argv_tries[-1:]  # last entry only
        for argv in argv_tries:
            try:
                if argv[0] == 'su':
                    sys.stderr.write('[local su] ')
                self.p = ssubprocess.Popen(argv, stdout=s1, preexec_fn=setup)
                # No env: Talking to `FirewallClient.start`, which has no i18n.
                break
            except OSError as e:
                log('Spawning firewall manager: %r' % argv)
                raise Fatal(e)
        self.argv = argv
        s1.close()
        self.pfile = s2.makefile('rwb')
        line = self.pfile.readline()
        self.check()
        if line[0:5] != b'READY':
            raise Fatal('%r expected READY, got %r' % (self.argv, line))
        method_name = line[6:-1]
        self.method = get_method(method_name.decode("ASCII"))
        self.method.set_firewall(self)
Exemple #7
0
    def __init__(self, method_name, sudo_pythonpath):
        self.auto_nets = []
        python_path = os.path.dirname(os.path.dirname(__file__))
        argvbase = ([sys.executable, sys.argv[0]] +
                    ['-v'] * (helpers.verbose or 0) +
                    ['--method', method_name] +
                    ['--firewall'])
        if ssyslog._p:
            argvbase += ['--syslog']
        # Default to sudo unless on OpenBSD in which case use built in `doas`
        if platform.platform().startswith('OpenBSD'):
            elev_prefix = ['doas']
        else:
            elev_prefix = ['sudo', '-p', '[local sudo] Password: '******'/usr/bin/env',
                            'PYTHONPATH=%s' % python_path]
        argv_tries = [elev_prefix + argvbase, argvbase]

        # we can't use stdin/stdout=subprocess.PIPE here, as we normally would,
        # because stupid Linux 'su' requires that stdin be attached to a tty.
        # Instead, attach a *bidirectional* socket to its stdout, and use
        # that for talking in both directions.
        (s1, s2) = socket.socketpair()

        def setup():
            # run in the child process
            s2.close()
        e = None
        if os.getuid() == 0:
            argv_tries = argv_tries[-1:]  # last entry only
        for argv in argv_tries:
            try:
                if argv[0] == 'su':
                    sys.stderr.write('[local su] ')
                self.p = ssubprocess.Popen(argv, stdout=s1, preexec_fn=setup)
                # No env: Talking to `FirewallClient.start`, which has no i18n.
                e = None
                break
            except OSError:
                pass
        self.argv = argv
        s1.close()
        if sys.version_info < (3, 0):
            # python 2.7
            self.pfile = s2.makefile('wb+')
        else:
            # python 3.5
            self.pfile = s2.makefile('rwb')
        if e:
            log('Spawning firewall manager: %r\n' % self.argv)
            raise Fatal(e)
        line = self.pfile.readline()
        self.check()
        if line[0:5] != b'READY':
            raise Fatal('%r expected READY, got %r' % (self.argv, line))
        method_name = line[6:-1]
        self.method = get_method(method_name.decode("ASCII"))
        self.method.set_firewall(self)
Exemple #8
0
 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
Exemple #9
0
 def initializeChannelHandlers(self):
     try:
         for item in self.redisPubSub.listen():
             self.handlePubSubEvent(item)
     except redis.ConnectionError as e:
         log("Something happened with the established redis connection: %s -- reconnecting\n"
             % e)
         self.reconnect()
Exemple #10
0
 def callback(self, sock):
     log('--no callback defined-- %r\n' % self)
     (r, w, x) = select.select(self.socks, [], [], 0)
     for s in r:
         v = s.recv(4096)
         if not v:
             log('--closed-- %r\n' % self)
             self.socks = []
             self.ok = False
Exemple #11
0
 def callback(self, sock):
     try:
         data, peer = sock.recvfrom(4096)
     except socket.error as e:
         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)
Exemple #12
0
 def callback(self, sock):
     log('--no callback defined-- %r\n' % self)
     (r, _, _) = select.select(self.socks, [], [], 0)
     for s in r:
         v = s.recv(4096)
         if not v:
             log('--closed-- %r\n' % self)
             self.socks = []
             self.ok = False
Exemple #13
0
 def callback(self, sock):
     try:
         data, peer = sock.recvfrom(4096)
     except socket.error as e:
         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)
Exemple #14
0
def _check_smb(hostname):
    return
    global _smb_ok
    if not _smb_ok:
        return
    argv = ['smbclient', '-U', '%', '-L', hostname]
    debug2(' > smb: %s\n' % hostname)
    try:
        p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
        lines = p.stdout.readlines()
        p.wait()
    except OSError:
        _, e = sys.exc_info()[:2]
        log('%r failed: %r\n' % (argv, e))
        _smb_ok = False
        return

    lines.reverse()

    # junk at top
    while lines:
        line = lines.pop().strip()
        if re.match(r'Server\s+', line):
            break

    # server list section:
    #    Server   Comment
    #    ------   -------
    while lines:
        line = lines.pop().strip()
        if not line or re.match(r'-+\s+-+', line):
            continue
        if re.match(r'Workgroup\s+Master', line):
            break
        words = line.split()
        hostname = words[0].lower()
        debug3('<    %s\n' % hostname)
        check_host(hostname)

    # workgroup list section:
    #   Workgroup  Master
    #   ---------  ------
    while lines:
        line = lines.pop().strip()
        if re.match(r'-+\s+', line):
            continue
        if not line:
            break
        words = line.split()
        (workgroup, hostname) = (words[0].lower(), words[1].lower())
        debug3('<    group(%s) -> %s\n' % (workgroup, hostname))
        check_host(hostname)
        check_workgroup(workgroup)

    if lines:
        assert(0)
Exemple #15
0
def _check_smb(hostname):
    return
    global _smb_ok
    if not _smb_ok:
        return
    argv = ['smbclient', '-U', '%', '-L', hostname]
    debug2(' > smb: %s\n' % hostname)
    try:
        p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
        lines = p.stdout.readlines()
        p.wait()
    except OSError:
        _, e = sys.exc_info()[:2]
        log('%r failed: %r\n' % (argv, e))
        _smb_ok = False
        return

    lines.reverse()

    # junk at top
    while lines:
        line = lines.pop().strip()
        if re.match(r'Server\s+', line):
            break

    # server list section:
    #    Server   Comment
    #    ------   -------
    while lines:
        line = lines.pop().strip()
        if not line or re.match(r'-+\s+-+', line):
            continue
        if re.match(r'Workgroup\s+Master', line):
            break
        words = line.split()
        hostname = words[0].lower()
        debug3('<    %s\n' % hostname)
        check_host(hostname)

    # workgroup list section:
    #   Workgroup  Master
    #   ---------  ------
    while lines:
        line = lines.pop().strip()
        if re.match(r'-+\s+', line):
            continue
        if not line:
            break
        words = line.split()
        (workgroup, hostname) = (words[0].lower(), words[1].lower())
        debug3('<    group(%s) -> %s\n' % (workgroup, hostname))
        check_host(hostname)
        check_workgroup(workgroup)

    if lines:
        assert (0)
Exemple #16
0
def _check_smb(hostname):
    return
    global _smb_ok
    if not _smb_ok:
        return
    argv = ["smbclient", "-U", "%", "-L", hostname]
    debug2(" > smb: %s\n" % hostname)
    try:
        p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
        lines = p.stdout.readlines()
        p.wait()
    except OSError as e:
        log("%r failed: %r\n" % (argv, e))
        _smb_ok = False
        return

    lines.reverse()

    # junk at top
    while lines:
        line = lines.pop().strip()
        if re.match(r"Server\s+", line):
            break

    # server list section:
    #    Server   Comment
    #    ------   -------
    while lines:
        line = lines.pop().strip()
        if not line or re.match(r"-+\s+-+", line):
            continue
        if re.match(r"Workgroup\s+Master", line):
            break
        words = line.split()
        hostname = words[0].lower()
        debug3("<    %s\n" % hostname)
        check_host(hostname)

    # workgroup list section:
    #   Workgroup  Master
    #   ---------  ------
    while lines:
        line = lines.pop().strip()
        if re.match(r"-+\s+", line):
            continue
        if not line:
            break
        words = line.split()
        (workgroup, hostname) = (words[0].lower(), words[1].lower())
        debug3("<    group(%s) -> %s\n" % (workgroup, hostname))
        check_host(hostname)
        check_workgroup(workgroup)

    if lines:
        assert 0
Exemple #17
0
    def __init__(self, method_name):
        self.auto_nets = []
        python_path = os.path.dirname(os.path.dirname(__file__))
        argvbase = ([sys.executable, sys.argv[0]] +
                    ['-v'] * (helpers.verbose or 0) +
                    ['--method', method_name] +
                    ['--firewall'])
        if ssyslog._p:
            argvbase += ['--syslog']
        argv_tries = [
            ['sudo', '-p', '[local sudo] Password: '******'PYTHONPATH=%s' % python_path), '--'] + argvbase,
            argvbase
        ]

        # we can't use stdin/stdout=subprocess.PIPE here, as we normally would,
        # because stupid Linux 'su' requires that stdin be attached to a tty.
        # Instead, attach a *bidirectional* socket to its stdout, and use
        # that for talking in both directions.
        (s1, s2) = socket.socketpair()

        def setup():
            # run in the child process
            s2.close()
        e = None
        if os.getuid() == 0:
            argv_tries = argv_tries[-1:]  # last entry only
        for argv in argv_tries:
            try:
                if argv[0] == 'su':
                    sys.stderr.write('[local su] ')
                self.p = ssubprocess.Popen(argv, stdout=s1, preexec_fn=setup)
                e = None
                break
            except OSError as e:
                pass
        self.argv = argv
        s1.close()
        if sys.version_info < (3, 0):
            # python 2.7
            self.pfile = s2.makefile('wb+')
        else:
            # python 3.5
            self.pfile = s2.makefile('rwb')
        if e:
            log('Spawning firewall manager: %r\n' % self.argv)
            raise Fatal(e)
        line = self.pfile.readline()
        self.check()
        if line[0:5] != b'READY':
            raise Fatal('%r expected READY, got %r' % (self.argv, line))
        method_name = line[6:-1]
        self.method = get_method(method_name.decode("ASCII"))
        self.method.set_firewall(self)
Exemple #18
0
    def reload_always_connected(self):
        global _always_connected

        if self.acl is not None:
            _always_connected = self.acl
        else:
            _always_connected = ALWAYS_CONNECTED_OFF

        if (_always_connected == ALWAYS_CONNECTED_OFF):
            log("alwaysConnected mode is OFF")
        else:
            log("alwaysConnected mode is ON")
Exemple #19
0
def list_routes():
    if which('ip'):
        routes = _list_routes(['ip', 'route'], _route_iproute)
    elif which('netstat'):
        routes = _list_routes(['netstat', '-rn'], _route_netstat)
    else:
        log('WARNING: Neither ip nor netstat were found on the server.\n')
        routes = []

    for (family, ip, width) in routes:
        if not ip.startswith('0.') and not ip.startswith('127.'):
            yield (family, ip, width)
Exemple #20
0
def list_routes():
    if which('ip'):
        routes = _list_routes(['ip', 'route'], _route_iproute)
    elif which('netstat'):
        routes = _list_routes(['netstat', '-rn'], _route_netstat)
    else:
        log('WARNING: Neither ip nor netstat were found on the server.\n')
        routes = []

    for (family, ip, width) in routes:
        if not ip.startswith('0.') and not ip.startswith('127.'):
            yield (family, ip, width)
Exemple #21
0
def list_routes():
    if which('ip'):
        routes = _list_routes(['ip', 'route'], _route_iproute)
    elif which('netstat'):
        routes = _list_routes(['netstat', '-rn'], _route_netstat)
    else:
        log('WARNING: Neither "ip" nor "netstat" were found on the server. '
            '--auto-nets feature will not work.')
        routes = []

    for (family, ip, width) in routes:
        if not ip.startswith('0.') and not ip.startswith('127.'):
            yield (family, ip, width)
Exemple #22
0
def _check_netstat():
    debug2(' > netstat\n')
    argv = ['netstat', '-n']
    try:
        p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
        content = p.stdout.read()
        p.wait()
    except OSError as e:
        log('%r failed: %r\n' % (argv, e))
        return

    for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content):
        debug3('<    %s\n' % ip)
        check_host(ip)
Exemple #23
0
def _check_netstat():
    debug2(' > netstat\n')
    argv = ['netstat', '-n']
    try:
        p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
        content = p.stdout.read().decode("ASCII")
        p.wait()
    except OSError as e:
        log('%r failed: %r\n' % (argv, e))
        return

    for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content):
        debug3('<    %s\n' % ip)
        check_host(ip)
Exemple #24
0
def _check_netstat():
    debug2(" > netstat\n")
    argv = ["netstat", "-n"]
    try:
        p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
        content = p.stdout.read()
        p.wait()
    except OSError as e:
        log("%r failed: %r\n" % (argv, e))
        return

    for ip in re.findall(r"\d+\.\d+\.\d+\.\d+", content):
        debug3("<    %s\n" % ip)
        check_host(ip)
Exemple #25
0
def ipfw_rule_exists(n):
    argv = ['ipfw', 'list']
    p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=get_env())

    found = False
    for line in p.stdout:
        if line.startswith(b'%05d ' % n):
            if not ('ipttl 63' in line or 'check-state' in line):
                log('non-sshuttle ipfw rule: %r' % line.strip())
                raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n)
            found = True
    rv = p.wait()
    if rv:
        raise Fatal('%r returned %d' % (argv, rv))
    return found
Exemple #26
0
def ipt_ttl(family, *args):
    global _no_ttl_module
    if not _no_ttl_module:
        # we avoid infinite loops by generating server-side connections
        # with ttl 42.  This makes the client side not recapture those
        # connections, in case client == server.
        try:
            argsplus = list(args) + ["-m", "ttl", "!", "--ttl", "42"]
            ipt(family, *argsplus)
        except Fatal:
            ipt(family, *args)
            # we only get here if the non-ttl attempt succeeds
            log("sshuttle: warning: your iptables is missing " "the ttl module.\n")
            _no_ttl_module = True
    else:
        ipt(family, *args)
Exemple #27
0
    def maybe_resume(self, wrote):
        self.buf_total = self.buf_total - wrote

        global _global_mux_wrapper_buffer_size
        _global_mux_wrapper_buffer_size -= wrote

        debug3(
            'Global mux wrap buf size: %d. Individual buf size: %d. On channel %s\n'
            % (_global_mux_wrapper_buffer_size, self.buf_total, self.channel))

        if self.isPaused and self.buf_total < INDIVIDUAL_BUFFER_RESUME_SIZE:
            self.mux.send(self.channel, CMD_RESUME, b(''))
            self.isPaused = False
            log('Global mux wrap buf size: %d. Individual buf size: %d (below threshold). Sent CMD_RESUME on channel %s\n'
                % (_global_mux_wrapper_buffer_size, self.buf_total,
                   self.channel))
Exemple #28
0
def ipt_ttl(family, *args):
    global _no_ttl_module
    if not _no_ttl_module:
        # we avoid infinite loops by generating server-side connections
        # with ttl 63.  This makes the client side not recapture those
        # connections, in case client == server.
        try:
            argsplus = list(args) + ['-m', 'ttl', '!', '--ttl', '63']
            ipt(family, *argsplus)
        except Fatal:
            ipt(family, *args)
            # we only get here if the non-ttl attempt succeeds
            log('fw: WARNING: your iptables is missing ' 'the ttl module.\n')
            _no_ttl_module = True
    else:
        ipt(family, *args)
Exemple #29
0
def ipfw_rule_exists(n):
    argv = ['ipfw', 'list']
    p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
    found = False
    for line in p.stdout:
        if line.startswith('%05d ' % n):
            if not ('ipttl 42' in line
                    or ('skipto %d' % (n + 1)) in line
                    or 'check-state' in line):
                log('non-sshuttle ipfw rule: %r\n' % line.strip())
                raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n)
            found = True
    rv = p.wait()
    if rv:
        raise Fatal('%r returned %d' % (argv, rv))
    return found
Exemple #30
0
def onaccept_tcp(listener, method, mux, handlers):
    global _extra_fd
    try:
        sock, srcip = listener.accept()
    except socket.error as e:
        if e.args[0] in [errno.EMFILE, errno.ENFILE]:
            debug1('Rejected incoming connection: too many open files!\n')
            # free up an fd so we can eat the connection
            os.close(_extra_fd)
            try:
                sock, srcip = listener.accept()
                sock.close()
            finally:
                _extra_fd = os.open('/dev/null', os.O_RDONLY)
            return
        else:
            raise

    dstip = method.get_tcp_dstip(sock)

    if not tcp_connection_is_allowed(dstip[0], str(dstip[1]), srcip[0]):
        debug1('Deny TCP: %s:%r -> %s:%r.\n' %
               (srcip[0], srcip[1], dstip[0], dstip[1]))
        sock.close()
        return

    debug1('Accept TCP: %s:%r -> %s:%r.\n' %
           (srcip[0], srcip[1], dstip[0], dstip[1]))
    if dstip[1] == sock.getsockname()[1] and islocal(dstip[0], sock.family):
        debug1("-- ignored: that's my address!\n")
        sock.close()
        return
    chan = mux.next_channel()
    if not chan:
        log('warning: too many open channels.  Discarded connection.\n')
        sock.close()
        return
    mux.send(chan, ssnet.CMD_TCP_CONNECT,
             b'%d,%s,%d' % (sock.family, dstip[0].encode("ASCII"), dstip[1]))
    outwrap = MuxWrapper(mux, chan)
    s = Proxy(
        SockWrapper(sock, sock, None, None,
                    lambda: connection_is_active(sock)), outwrap)
    handlers.append(s)
    active_tcp_conns[sock] = True
    tcp_conns.append((srcip, dstip, s, sock))
    expire_connections(time.time(), mux)
Exemple #31
0
def save_config(content, file_name):
    process = Popen([
        'sudo env "PATH=$PATH" sudoers-add "%(fn)s"' % {"fn": file_name},
    ], stdout=PIPE, stdin=PIPE, shell=True)

    process.stdin.write(content.encode())

    streamdata = process.communicate()[0]
    returncode = process.returncode

    if returncode:
        log('Failed updating sudoers file.\n');
        debug1(streamdata)
        exit(returncode)
    else:
        log('Success, sudoers file update.\n')
        exit(0)
Exemple #32
0
 def got_packet(self, channel, cmd, data):
     debug2('<  channel=%d cmd=%s len=%d\n'
            % (channel, cmd_to_name.get(cmd, hex(cmd)), len(data)))
     if cmd == CMD_PING:
         self.send(0, CMD_PONG, data)
     elif cmd == CMD_PONG:
         debug2('received PING response\n')
         self.too_full = False
         self.fullness = 0
     elif cmd == CMD_EXIT:
         self.ok = False
     elif cmd == CMD_TCP_CONNECT:
         assert(not self.channels.get(channel))
         if self.new_channel:
             self.new_channel(channel, data)
     elif cmd == CMD_DNS_REQ:
         assert(not self.channels.get(channel))
         if self.got_dns_req:
             self.got_dns_req(channel, data)
     elif cmd == CMD_UDP_OPEN:
         assert(not self.channels.get(channel))
         if self.got_udp_open:
             self.got_udp_open(channel, data)
     elif cmd == CMD_ROUTES:
         if self.got_routes:
             self.got_routes(data)
         else:
             raise Exception('got CMD_ROUTES without got_routes?')
     elif cmd == CMD_HOST_REQ:
         if self.got_host_req:
             self.got_host_req(data)
         else:
             raise Exception('got CMD_HOST_REQ without got_host_req?')
     elif cmd == CMD_HOST_LIST:
         if self.got_host_list:
             self.got_host_list(data)
         else:
             raise Exception('got CMD_HOST_LIST without got_host_list?')
     else:
         callback = self.channels.get(channel)
         if not callback:
             log('warning: closed channel %d got cmd=%s len=%d\n'
                 % (channel, cmd_to_name.get(cmd, hex(cmd)), len(data)))
         else:
             callback(cmd, data)
Exemple #33
0
 def got_packet(self, channel, cmd, data):
     debug2('<  channel=%d cmd=%s len=%d\n'
            % (channel, cmd_to_name.get(cmd, hex(cmd)), len(data)))
     if cmd == CMD_PING:
         self.send(0, CMD_PONG, data)
     elif cmd == CMD_PONG:
         debug2('received PING response\n')
         self.too_full = False
         self.fullness = 0
     elif cmd == CMD_EXIT:
         self.ok = False
     elif cmd == CMD_TCP_CONNECT:
         assert(not self.channels.get(channel))
         if self.new_channel:
             self.new_channel(channel, data)
     elif cmd == CMD_DNS_REQ:
         assert(not self.channels.get(channel))
         if self.got_dns_req:
             self.got_dns_req(channel, data)
     elif cmd == CMD_UDP_OPEN:
         assert(not self.channels.get(channel))
         if self.got_udp_open:
             self.got_udp_open(channel, data)
     elif cmd == CMD_ROUTES:
         if self.got_routes:
             self.got_routes(data)
         else:
             raise Exception('got CMD_ROUTES without got_routes?')
     elif cmd == CMD_HOST_REQ:
         if self.got_host_req:
             self.got_host_req(data)
         else:
             raise Exception('got CMD_HOST_REQ without got_host_req?')
     elif cmd == CMD_HOST_LIST:
         if self.got_host_list:
             self.got_host_list(data)
         else:
             raise Exception('got CMD_HOST_LIST without got_host_list?')
     else:
         callback = self.channels.get(channel)
         if not callback:
             log('warning: closed channel %d got cmd=%s len=%d\n'
                 % (channel, cmd_to_name.get(cmd, hex(cmd)), len(data)))
         else:
             callback(cmd, data)
Exemple #34
0
def _handle_diversion(divertsock, dnsport):
    p, tag = divertsock.recvfrom(4096)
    src, dst = _udp_unpack(p)
    debug3('got diverted packet from %r to %r\n' % (src, dst))
    if dst[1] == 53:
        # outgoing DNS
        debug3('...packet is a DNS request.\n')
        _real_dns_server[0] = dst
        dst = ('127.0.0.1', dnsport)
    elif src[1] == dnsport:
        if islocal(src[0], divertsock.family):
            debug3('...packet is a DNS response.\n')
            src = _real_dns_server[0]
    else:
        log('weird?! unexpected divert from %r to %r\n' % (src, dst))
        assert(0)
    newp = _udp_repack(p, src, dst)
    divertsock.sendto(newp, tag)
Exemple #35
0
def _handle_diversion(divertsock, dnsport):
    p, tag = divertsock.recvfrom(4096)
    src, dst = _udp_unpack(p)
    debug3('got diverted packet from %r to %r\n' % (src, dst))
    if dst[1] == 53:
        # outgoing DNS
        debug3('...packet is a DNS request.\n')
        _real_dns_server[0] = dst
        dst = ('127.0.0.1', dnsport)
    elif src[1] == dnsport:
        if islocal(src[0], divertsock.family):
            debug3('...packet is a DNS response.\n')
            src = _real_dns_server[0]
    else:
        log('weird?! unexpected divert from %r to %r\n' % (src, dst))
        assert (0)
    newp = _udp_repack(p, src, dst)
    divertsock.sendto(newp, tag)
Exemple #36
0
def _check_netstat():
    debug2(' > netstat')
    argv = ['netstat', '-n']
    try:
        p = ssubprocess.Popen(argv,
                              stdout=ssubprocess.PIPE,
                              stderr=null,
                              env=get_env())
        content = p.stdout.read().decode("ASCII")
        p.wait()
    except OSError:
        _, e = sys.exc_info()[:2]
        log('%r failed: %r' % (argv, e))
        return

    for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content):
        debug3('<    %s' % ip)
        check_host(ip)
Exemple #37
0
def matches_acl(dstip, dstport, store_to_check):
    if store_to_check is None:
        return False

    debug3('Checking for global IP rule ...')

    if (acl_entry_match('0.0.0.0/0', dstport, store_to_check)):
        debug3('Matched global IP rule')
        return True

    debug3('No global IP rule')
    debug3('Checking for single IP rule ...')

    cidr_for_single_ip = dstip + '/32'

    if (acl_entry_match(cidr_for_single_ip, dstport, store_to_check)):
        debug3('Matched single IP rule')
        return True

    debug3('No single IP rule')
    debug3('Checking for IP range rule (subnet/cidr block) ...')

    for cidr_entry in store_to_check:
        mask = int(cidr_entry.split('/')[1])
        is_range_rule = (cidr_entry != '0.0.0.0/0') and (mask != 32)

        if is_range_rule:
            try:
                acl_subnet = ipaddress.ip_network(cidr_entry, False)

                destination = ipaddress.ip_address(dstip)

                if destination in acl_subnet:
                    if (acl_port_match(dstport, store_to_check[cidr_entry])):
                        debug3('Matched IP range rule')
                        return True
            except:
                log('Failed to parse CIDR block %s' % cidr_entry)

    debug3('No IP range rule')

    debug3('Destination did not match the ACL')

    return False
Exemple #38
0
def _list_routes(argv, extract_route):
    # FIXME: IPv4 only
    p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=get_env())
    routes = []
    for line in p.stdout:
        if not line.strip():
            continue
        ipw, mask = extract_route(line.decode("ASCII"))
        if not ipw:
            continue
        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' % (argv, rv))

    return routes
Exemple #39
0
    def reload_acl_targets_file(self):

        global _allowed_targets

        if self.acl is not None:
            try:
                _new_targets = json.loads(self.acl, "utf-8")
                _allowed_targets = _new_targets
            except BaseException as e:
                debug3(
                    "An exception has occurred while loading the allowed targets (sshuttleAcl) data: {}\n\n"
                    .format(e))
        else:
            _allowed_targets = None

        if (not _allowed_targets):
            log("Allowed ACL list is empty. Restricting all access\n")
        else:
            log("Network Connection Allowed ACL \n\n%s" % _allowed_targets)
Exemple #40
0
def save_config(content, file_name):
    process = Popen([
        '/usr/bin/sudo',
        spawn.find_executable('sudoers-add'),
        file_name,
    ], stdout=PIPE, stdin=PIPE)

    process.stdin.write(content.encode())

    streamdata = process.communicate()[0]
    returncode = process.returncode

    if returncode:
        log('Failed updating sudoers file.')
        debug1(streamdata)
        exit(returncode)
    else:
        log('Success, sudoers file update.')
        exit(0)
Exemple #41
0
def ipfw_rule_exists(n):
    argv = ['ipfw', 'list']
    env = {
        'PATH': os.environ['PATH'],
        'LC_ALL': "C",
    }
    p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)

    found = False
    for line in p.stdout:
        if line.startswith(b'%05d ' % n):
            if not ('ipttl 42' in line or 'check-state' in line):
                log('non-sshuttle ipfw rule: %r\n' % line.strip())
                raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n)
            found = True
    rv = p.wait()
    if rv:
        raise Fatal('%r returned %d' % (argv, rv))
    return found
Exemple #42
0
    def maybe_pause(self, data):
        self.buf_total = self.buf_total + len(data)

        global _global_mux_wrapper_buffer_size
        _global_mux_wrapper_buffer_size += len(data)
        self.metrics.save_max_mux_wrapper_buffer_size(
            _global_mux_wrapper_buffer_size)

        debug3(
            'Global mux wrap buf size: %d. Individual buf size: %d. On channel %s\n'
            % (_global_mux_wrapper_buffer_size, self.buf_total, self.channel))

        if not self.isPaused and _global_mux_wrapper_buffer_size > PAUSE_TRAFFIC_TO_EDGE_THRESHOLD and self.buf_total\
                > INDIVIDUAL_BUFFER_PAUSE_SIZE:
            self.mux.send(self.channel, CMD_PAUSE, b(''))
            self.isPaused = True
            self.metrics.incr_paused_to_the_edge_count()
            log('Global mux wrap buf size: %d (above threshold). Individual buf size: %d (above threshold). Sent CMD_PAUSE on channel %s\n'
                % (_global_mux_wrapper_buffer_size, self.buf_total,
                   self.channel))
Exemple #43
0
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
Exemple #44
0
def start_hostwatch(seed_hosts, auto_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, auto_hosts) or 0
            except Exception:
                log('%s\n' % _exc_dump())
                rv = 98
        finally:
            os._exit(rv)
    s1.close()
    return pid, s2
Exemple #45
0
def _check_netstat():
    debug2(' > netstat\n')
    env = {
        'PATH': os.environ['PATH'],
        'LC_ALL': "C",
    }
    argv = ['netstat', '-n']
    try:
        p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null,
                              env=env)
        content = p.stdout.read().decode("ASCII")
        p.wait()
    except OSError:
        _, e = sys.exc_info()[:2]
        log('%r failed: %r\n' % (argv, e))
        return

    for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content):
        debug3('<    %s\n' % ip)
        check_host(ip)
Exemple #46
0
def _list_routes():
    argv = ['netstat', '-rn']
    p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
    routes = []
    for line in p.stdout:
        cols = re.split(r'\s+', line)
        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
Exemple #47
0
 def send_udp(self, sock, srcip, dstip, data):
     if not srcip:
         debug1("-- ignored UDP to %r: "
                "couldn't determine source IP address\n" % (dstip, ))
         return
     try:
         sender = socket.socket(sock.family, socket.SOCK_DGRAM)
         sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         sender.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1)
         # The bind function may throw 'Address already in use' error
         # if it happens, log the source ip.
         # Background info: The ntpd service caused this in
         # the past, so it is important to configure your ntpd server where the
         # sshuttle client runs to not listen on all IPs.  (i.e.
         # add "interface ignore wildcard" to the /etc/ntp.conf and restart the ntpd service)
         sender.bind(srcip)
         sender.sendto(data, dstip)
         sender.close()
     except OSError as e:
         log('WARNING: send_udp failed: %s -> %s.  Exception: %s\n' %
             (srcip, dstip, e.strerror))
Exemple #48
0
def read_host_cache():
    """If possible, read the cache file from disk to populate hosts that
       were found in a previous sshuttle run."""
    try:
        f = open(CACHEFILE)
    except (OSError, IOError):
        _, e = sys.exc_info()[:2]
        if e.errno == errno.ENOENT:
            return
        else:
            log("Failed to read existing host cache file %s on remote host"
                % CACHEFILE)
            return
    for line in f:
        words = line.strip().split(',')
        if len(words) == 2:
            (name, ip) = words
            name = re.sub(r'[^-\w\.]', '-', name).strip()
            # Remove characters that shouldn't be in IP
            ip = re.sub(r'[^0-9.]', '', ip).strip()
            if name and ip:
                found_host(name, ip)
Exemple #49
0
    def callback(self, sock):
        peer = self.peers[sock]

        try:
            data = sock.recv(4096)
        except socket.error as e:
            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
Exemple #50
0
def _list_routes(argv, extract_route):
    # FIXME: IPv4 only
    env = {
        'PATH': os.environ['PATH'],
        'LC_ALL': "C",
    }
    p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
    routes = []
    for line in p.stdout:
        if not line.strip():
            continue
        ipw, mask = extract_route(line.decode("ASCII"))
        if not ipw:
            continue
        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
Exemple #51
0
def nonfatal(func, *args):
    try:
        func(*args)
    except Fatal as e:
        log('error: %s\n' % e)
Exemple #52
0
import sshuttle.helpers as helpers
from sshuttle.helpers import log, debug1, debug2, debug3

POLL_TIME = 60 * 15
NETSTAT_POLL_TIME = 30
CACHEFILE = os.path.expanduser("~/.sshuttle.hosts")


_nmb_ok = True
_smb_ok = True
hostnames = {}
queue = {}
try:
    null = open("/dev/null", "wb")
except IOError as e:
    log("warning: %s\n" % e)
    null = os.popen("sh -c 'while read x; do :; done'", "wb", 4096)


def _is_ip(s):
    return re.match(r"\d+\.\d+\.\d+\.\d+$", s)


def write_host_cache():
    tmpname = "%s.%d.tmp" % (CACHEFILE, os.getpid())
    try:
        f = open(tmpname, "wb")
        for name, ip in sorted(hostnames.items()):
            f.write("%s,%s\n" % (name, ip))
        f.close()
        os.chmod(tmpname, 0o600)
Exemple #53
0
                    ipport_v6 = parse_ipport6(ip)
                else:
                    ipport_v4 = parse_ipport4(ip)
        return_code = client.main(ipport_v6, ipport_v4,
                                  opt.ssh_cmd,
                                  remotename,
                                  opt.python,
                                  opt.latency_control,
                                  opt.dns,
                                  nslist,
                                  method_name,
                                  sh,
                                  opt.auto_nets,
                                  parse_subnets(includes),
                                  parse_subnets(excludes),
                                  opt.syslog, opt.daemon, opt.pidfile)

        if return_code == 0:
            log('Normal exit code, exiting...')
        else:
            log('Abnormal exit code detected, failing...' % return_code)
        sys.exit(return_code)

except Fatal as e:
    log('fatal: %s\n' % e)
    sys.exit(99)
except KeyboardInterrupt:
    log('\n')
    log('Keyboard interrupt: exiting.\n')
    sys.exit(1)
Exemple #54
0
def got_signal(signum, frame):
    log('exiting on signal %d\n' % signum)
    sys.exit(1)
Exemple #55
0
def main():
    opt = parser.parse_args()

    if opt.daemon:
        opt.syslog = 1
    if opt.wrap:
        import sshuttle.ssnet as ssnet

        ssnet.MAX_CHANNEL = opt.wrap
    helpers.verbose = opt.verbose

    try:
        if opt.firewall:
            if opt.subnets or opt.subnets_file:
                parser.error("exactly zero arguments expected")
            return firewall.main(opt.method, opt.syslog)
        elif opt.hostwatch:
            return hostwatch.hw_main(opt.subnets)
        else:
            includes = opt.subnets + opt.subnets_file
            excludes = opt.exclude
            if not includes and not opt.auto_nets:
                parser.error("at least one subnet, subnet file, " "or -N expected")
            remotename = opt.remote
            if remotename == "" or remotename == "-":
                remotename = None
            nslist = [family_ip_tuple(ns) for ns in opt.ns_hosts]
            if opt.seed_hosts:
                sh = re.split(r"[\s,]+", (opt.seed_hosts or "").strip())
            elif opt.auto_hosts:
                sh = []
            else:
                sh = None
            if opt.listen:
                ipport_v6 = None
                ipport_v4 = None
                list = opt.listen.split(",")
                for ip in list:
                    if "[" in ip and "]" in ip:
                        ipport_v6 = parse_ipport6(ip)
                    else:
                        ipport_v4 = parse_ipport4(ip)
            else:
                # parse_ipport4('127.0.0.1:0')
                ipport_v4 = "auto"
                # parse_ipport6('[::1]:0')
                ipport_v6 = "auto" if not opt.disable_ipv6 else None
            if opt.syslog:
                ssyslog.start_syslog()
                ssyslog.stderr_to_syslog()
            return_code = client.main(
                ipport_v6,
                ipport_v4,
                opt.ssh_cmd,
                remotename,
                opt.python,
                opt.latency_control,
                opt.dns,
                nslist,
                opt.method,
                sh,
                opt.auto_hosts,
                opt.auto_nets,
                includes,
                excludes,
                opt.daemon,
                opt.pidfile,
            )

            if return_code == 0:
                log("Normal exit code, exiting...")
            else:
                log("Abnormal exit code detected, failing..." % return_code)
            return return_code

    except Fatal as e:
        log("fatal: %s\n" % e)
        return 99
    except KeyboardInterrupt:
        log("\n")
        log("Keyboard interrupt: exiting.\n")
        return 1
Exemple #56
0
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
          python, latency_control,
          dns_listener, seed_hosts, auto_nets, daemon):

    debug1('Starting client with Python version %s\n'
           % platform.python_version())

    method = fw.method

    handlers = []
    if helpers.verbose >= 1:
        helpers.logprefix = 'c : '
    else:
        helpers.logprefix = 'client: '
    debug1('connecting to server...\n')

    try:
        (serverproc, serversock) = ssh.connect(
            ssh_cmd, remotename, python,
            stderr=ssyslog._p and ssyslog._p.stdin,
            options=dict(latency_control=latency_control))
    except socket.error as e:
        if e.args[0] == errno.EPIPE:
            raise Fatal("failed to establish ssh session (1)")
        else:
            raise
    mux = Mux(serversock, serversock)
    handlers.append(mux)

    expected = b'SSHUTTLE0001'

    try:
        v = 'x'
        while v and v != b'\0':
            v = serversock.recv(1)
        v = 'x'
        while v and v != b'\0':
            v = serversock.recv(1)
        initstring = serversock.recv(len(expected))
    except socket.error as e:
        if e.args[0] == errno.ECONNRESET:
            raise Fatal("failed to establish ssh session (2)")
        else:
            raise

    rv = serverproc.poll()
    if rv:
        raise Fatal('server died with error code %d' % rv)

    if initstring != expected:
        raise Fatal('expected server init string %r; got %r'
                    % (expected, initstring))
    log('Connected.\n')
    sys.stdout.flush()
    if daemon:
        daemonize()
        log('daemonizing (%s).\n' % _pidname)

    def onroutes(routestr):
        if auto_nets:
            for line in routestr.strip().split(b'\n'):
                (family, ip, width) = line.split(b',', 2)
                family = int(family)
                width = int(width)
                ip = ip.decode("ASCII")
                if family == socket.AF_INET6 and tcp_listener.v6 is None:
                    debug2("Ignored auto net %d/%s/%d\n" % (family, ip, width))
                if family == socket.AF_INET and tcp_listener.v4 is None:
                    debug2("Ignored auto net %d/%s/%d\n" % (family, ip, width))
                else:
                    debug2("Adding auto net %d/%s/%d\n" % (family, ip, width))
                    fw.auto_nets.append((family, ip, width))

        # we definitely want to do this *after* starting ssh, or we might end
        # up intercepting the ssh connection!
        #
        # Moreover, now that we have the --auto-nets option, we have to wait
        # for the server to send us that message anyway.  Even if we haven't
        # set --auto-nets, we might as well wait for the message first, then
        # ignore its contents.
        mux.got_routes = None
        fw.start()
    mux.got_routes = onroutes

    def onhostlist(hostlist):
        debug2('got host list: %r\n' % hostlist)
        for line in hostlist.strip().split():
            if line:
                name, ip = line.split(b',', 1)
                fw.sethostip(name, ip)
    mux.got_host_list = onhostlist

    tcp_listener.add_handler(handlers, onaccept_tcp, method, mux)

    if udp_listener:
        udp_listener.add_handler(handlers, onaccept_udp, method, mux)

    if dns_listener:
        dns_listener.add_handler(handlers, ondns, method, mux)

    if seed_hosts is not None:
        debug1('seed_hosts: %r\n' % seed_hosts)
        mux.send(0, ssnet.CMD_HOST_REQ, str.encode('\n'.join(seed_hosts)))

    while 1:
        rv = serverproc.poll()
        if rv:
            raise Fatal('server died with error code %d' % rv)

        ssnet.runonce(handlers, mux)
        if latency_control:
            mux.check_fullness()
Exemple #57
0
    def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
        # IPv6 not supported
        if family not in [socket.AF_INET, ]:
            raise Exception(
                'Address family "%s" unsupported by ipfw method_name'
                % family_to_string(family))
        if udp:
            raise Exception("UDP not supported by ipfw method_name")

        sport = str(port)
        xsport = str(port + 1)

        # cleanup any existing rules
        if ipfw_rule_exists(port):
            ipfw('delete', sport)

        while _changedctls:
            name = _changedctls.pop()
            oldval = _oldctls[name]
            _sysctl_set(name, oldval)

        if subnets or dnsport:
            sysctl_set('net.inet.ip.fw.enable', 1)
            changed = sysctl_set('net.inet.ip.scopedroute', 0, permanent=True)
            if changed:
                log("\n"
                    "        WARNING: ONE-TIME NETWORK DISRUPTION:\n"
                    "        =====================================\n"
                    "sshuttle has changed a MacOS kernel setting to work around\n"
                    "a bug in MacOS 10.6.  This will cause your network to drop\n"
                    "within 5-10 minutes unless you restart your network\n"
                    "interface (change wireless networks or unplug/plug the\n"
                    "ethernet port) NOW, then restart sshuttle.  The fix is\n"
                    "permanent; you only have to do this once.\n\n")
                sys.exit(1)

            ipfw('add', sport, 'check-state', 'ip',
                 'from', 'any', 'to', 'any')

        if subnets:
            # create new subnet entries
            for f, swidth, sexclude, snet \
                    in sorted(subnets, key=lambda s: s[1], reverse=True):
                if sexclude:
                    ipfw('add', sport, 'skipto', xsport,
                         'tcp',
                         'from', 'any', 'to', '%s/%s' % (snet, swidth))
                else:
                    ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port,
                         'tcp',
                         'from', 'any', 'to', '%s/%s' % (snet, swidth),
                         'not', 'ipttl', '42', 'keep-state', 'setup')

        # This part is much crazier than it is on Linux, because MacOS (at
        # least 10.6, and probably other versions, and maybe FreeBSD too)
        # doesn't correctly fixup the dstip/dstport for UDP packets when it
        # puts them through a 'fwd' rule.  It also doesn't fixup the
        # srcip/srcport in the response packet.  In Linux iptables, all that
        # happens magically for us, so we just redirect the packets and relax.
        #
        # On MacOS, we have to fix the ports ourselves.  For that, we use a
        # 'divert' socket, which receives raw packets and lets us mangle them.
        #
        # Here's how it works.  Let's say the local DNS server is 1.1.1.1:53,
        # and the remote DNS server is 2.2.2.2:53, and the local transproxy
        # port is 10.0.0.1:12300, and a client machine is making a request from
        # 10.0.0.5:9999. We see a packet like this:
        #    10.0.0.5:9999 -> 1.1.1.1:53
        # Since the destip:port matches one of our local nameservers, it will
        # match a 'fwd' rule, thus grabbing it on the local machine.  However,
        # the local kernel will then see a packet addressed to *:53 and not
        # know what to do with it; there's nobody listening on port 53.  Thus,
        # we divert it, rewriting it into this:
        #    10.0.0.5:9999 -> 10.0.0.1:12300
        # This gets proxied out to the server, which sends it to 2.2.2.2:53,
        # and the answer comes back, and the proxy sends it back out like this:
        #    10.0.0.1:12300 -> 10.0.0.5:9999
        # But that's wrong!  The original machine expected an answer from
        # 1.1.1.1:53, so we have to divert the *answer* and rewrite it:
        #    1.1.1.1:53 -> 10.0.0.5:9999
        #
        # See?  Easy stuff.
        if dnsport:
            divertsock = socket.socket(socket.AF_INET, socket.SOCK_RAW,
                                       IPPROTO_DIVERT)
            divertsock.bind(('0.0.0.0', port))  # IP field is ignored

            for f, ip in [i for i in nslist if i[0] == family]:
                # relabel and then catch outgoing DNS requests
                ipfw('add', sport, 'divert', sport,
                     'udp',
                     'from', 'any', 'to', '%s/32' % ip, '53',
                     'not', 'ipttl', '42')
            # relabel DNS responses
            ipfw('add', sport, 'divert', sport,
                 'udp',
                 'from', 'any', str(dnsport), 'to', 'any',
                 'not', 'ipttl', '42')

            def do_wait():
                while 1:
                    r, w, x = select.select([sys.stdin, divertsock], [], [])
                    if divertsock in r:
                        _handle_diversion(divertsock, dnsport)
                    if sys.stdin in r:
                        return
        else:
            do_wait = None

        return do_wait
Exemple #58
0
def main(listenip_v6, listenip_v4,
         ssh_cmd, remotename, python, latency_control, dns, nslist,
         method_name, seed_hosts, auto_nets,
         subnets_include, subnets_exclude, daemon, pidfile):

    if daemon:
        try:
            check_daemon(pidfile)
        except Fatal as e:
            log("%s\n" % e)
            return 5
    debug1('Starting sshuttle proxy.\n')

    fw = FirewallClient(method_name)

    # Get family specific subnet lists
    if dns:
        nslist += resolvconf_nameservers()

    subnets = subnets_include + subnets_exclude  # we don't care here
    subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
    nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6]
    subnets_v4 = [i for i in subnets if i[0] == socket.AF_INET]
    nslist_v4 = [i for i in nslist if i[0] == socket.AF_INET]

    # Check features available
    avail = fw.method.get_supported_features()
    required = Features()

    if listenip_v6 == "auto":
        if avail.ipv6:
            listenip_v6 = ('::1', 0)
        else:
            listenip_v6 = None

    required.ipv6 = len(subnets_v6) > 0 or listenip_v6 is not None
    required.udp = avail.udp
    required.dns = len(nslist) > 0

    # if IPv6 not supported, ignore IPv6 DNS servers
    if not required.ipv6:
        nslist_v6 = []
        nslist = nslist_v4

    fw.method.assert_features(required)

    if required.ipv6 and listenip_v6 is None:
        raise Fatal("IPv6 required but not listening.")

    # display features enabled
    debug1("IPv6 enabled: %r\n" % required.ipv6)
    debug1("UDP enabled: %r\n" % required.udp)
    debug1("DNS enabled: %r\n" % required.dns)

    # bind to required ports
    if listenip_v4 == "auto":
        listenip_v4 = ('127.0.0.1', 0)

    if listenip_v6 and listenip_v6[1] and listenip_v4 and listenip_v4[1]:
        # if both ports given, no need to search for a spare port
        ports = [0, ]
    else:
        # if at least one port missing, we have to search
        ports = range(12300, 9000, -1)

    # search for free ports and try to bind
    last_e = None
    redirectport_v6 = 0
    redirectport_v4 = 0
    bound = False
    debug2('Binding redirector:')
    for port in ports:
        debug2(' %d' % port)
        tcp_listener = MultiListener()

        if required.udp:
            udp_listener = MultiListener(socket.SOCK_DGRAM)
        else:
            udp_listener = None

        if listenip_v6 and listenip_v6[1]:
            lv6 = listenip_v6
            redirectport_v6 = lv6[1]
        elif listenip_v6:
            lv6 = (listenip_v6[0], port)
            redirectport_v6 = port
        else:
            lv6 = None
            redirectport_v6 = 0

        if listenip_v4 and listenip_v4[1]:
            lv4 = listenip_v4
            redirectport_v4 = lv4[1]
        elif listenip_v4:
            lv4 = (listenip_v4[0], port)
            redirectport_v4 = port
        else:
            lv4 = None
            redirectport_v4 = 0

        try:
            tcp_listener.bind(lv6, lv4)
            if udp_listener:
                udp_listener.bind(lv6, lv4)
            bound = True
            break
        except socket.error as e:
            if e.errno == errno.EADDRINUSE:
                last_e = e
            else:
                raise e

    debug2('\n')
    if not bound:
        assert(last_e)
        raise last_e
    tcp_listener.listen(10)
    tcp_listener.print_listening("TCP redirector")
    if udp_listener:
        udp_listener.print_listening("UDP redirector")

    bound = False
    if required.dns:
        # search for spare port for DNS
        debug2('Binding DNS:')
        ports = range(12300, 9000, -1)
        for port in ports:
            debug2(' %d' % port)
            dns_listener = MultiListener(socket.SOCK_DGRAM)

            if listenip_v6:
                lv6 = (listenip_v6[0], port)
                dnsport_v6 = port
            else:
                lv6 = None
                dnsport_v6 = 0

            if listenip_v4:
                lv4 = (listenip_v4[0], port)
                dnsport_v4 = port
            else:
                lv4 = None
                dnsport_v4 = 0

            try:
                dns_listener.bind(lv6, lv4)
                bound = True
                break
            except socket.error as e:
                if e.errno == errno.EADDRINUSE:
                    last_e = e
                else:
                    raise e
        debug2('\n')
        dns_listener.print_listening("DNS")
        if not bound:
            assert(last_e)
            raise last_e
    else:
        dnsport_v6 = 0
        dnsport_v4 = 0
        dns_listener = None

    # Last minute sanity checks.
    # These should never fail.
    # If these do fail, something is broken above.
    if len(subnets_v6) > 0:
        assert required.ipv6
        if redirectport_v6 == 0:
            raise Fatal("IPv6 subnets defined but not listening")

    if len(nslist_v6) > 0:
        assert required.dns
        assert required.ipv6
        if dnsport_v6 == 0:
            raise Fatal("IPv6 ns servers defined but not listening")

    if len(subnets_v4) > 0:
        if redirectport_v4 == 0:
            raise Fatal("IPv4 subnets defined but not listening")

    if len(nslist_v4) > 0:
        if dnsport_v4 == 0:
            raise Fatal("IPv4 ns servers defined but not listening")

    # setup method specific stuff on listeners
    fw.method.setup_tcp_listener(tcp_listener)
    if udp_listener:
        fw.method.setup_udp_listener(udp_listener)
    if dns_listener:
        fw.method.setup_udp_listener(dns_listener)

    # start the firewall
    fw.setup(subnets_include, subnets_exclude, nslist,
             redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
             required.udp)

    # start the client process
    try:
        return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
                     python, latency_control, dns_listener,
                     seed_hosts, auto_nets, daemon)
    finally:
        try:
            if daemon:
                # it's not our child anymore; can't waitpid
                fw.p.returncode = 0
            fw.done()
        finally:
            if daemon:
                daemon_cleanup()
Exemple #59
0
from sshuttle.helpers import log, debug1, debug2, debug3

POLL_TIME = 60 * 15
NETSTAT_POLL_TIME = 30
CACHEFILE = os.path.expanduser('~/.sshuttle.hosts')


_nmb_ok = True
_smb_ok = True
hostnames = {}
queue = {}
try:
    null = open('/dev/null', 'wb')
except IOError:
    _, e = sys.exc_info()[:2]
    log('warning: %s\n' % e)
    null = os.popen("sh -c 'while read x; do :; done'", 'wb', 4096)


def _is_ip(s):
    return re.match(r'\d+\.\d+\.\d+\.\d+$', s)


def write_host_cache():
    tmpname = '%s.%d.tmp' % (CACHEFILE, os.getpid())
    try:
        f = open(tmpname, 'wb')
        for name, ip in sorted(hostnames.items()):
            f.write(('%s,%s\n' % (name, ip)).encode("ASCII"))
        f.close()
        os.chmod(tmpname, 384)  # 600 in octal, 'rw-------'
Exemple #60
0
def main():
    opt = parser.parse_args()

    if opt.daemon:
        opt.syslog = 1
    if opt.wrap:
        import sshuttle.ssnet as ssnet
        ssnet.MAX_CHANNEL = opt.wrap
    helpers.verbose = opt.verbose

    try:
        if opt.firewall:
            if opt.subnets or opt.subnets_file:
                parser.error('exactly zero arguments expected')
            return firewall.main(opt.method, opt.syslog)
        elif opt.hostwatch:
            return hostwatch.hw_main(opt.subnets, opt.auto_hosts)
        else:
            includes = opt.subnets + opt.subnets_file
            excludes = opt.exclude
            if not includes and not opt.auto_nets:
                parser.error('at least one subnet, subnet file, '
                             'or -N expected')
            remotename = opt.remote
            if remotename == '' or remotename == '-':
                remotename = None
            nslist = [family_ip_tuple(ns) for ns in opt.ns_hosts]
            if opt.seed_hosts:
                sh = re.split(r'[\s,]+', (opt.seed_hosts or "").strip())
            elif opt.auto_hosts:
                sh = []
            else:
                sh = None
            if opt.listen:
                ipport_v6 = None
                ipport_v4 = None
                lst = opt.listen.split(",")
                for ip in lst:
                    family, ip, port = parse_ipport(ip)
                    if family == socket.AF_INET6:
                        ipport_v6 = (ip, port)
                    else:
                        ipport_v4 = (ip, port)
            else:
                # parse_ipport4('127.0.0.1:0')
                ipport_v4 = "auto"
                # parse_ipport6('[::1]:0')
                ipport_v6 = "auto" if not opt.disable_ipv6 else None
            if opt.syslog:
                ssyslog.start_syslog()
                ssyslog.close_stdin()
                ssyslog.stdout_to_syslog()
                ssyslog.stderr_to_syslog()
            return_code = client.main(ipport_v6, ipport_v4,
                                      opt.ssh_cmd,
                                      remotename,
                                      opt.python,
                                      opt.latency_control,
                                      opt.dns,
                                      nslist,
                                      opt.method,
                                      sh,
                                      opt.auto_hosts,
                                      opt.auto_nets,
                                      includes,
                                      excludes,
                                      opt.daemon,
                                      opt.to_ns,
                                      opt.pidfile,
                                      opt.user,
                                      opt.sudo_pythonpath)

            if return_code == 0:
                log('Normal exit code, exiting...')
            else:
                log('Abnormal exit code %d detected, failing...' % return_code)
            return return_code

    except Fatal as e:
        log('fatal: %s\n' % e)
        return 99
    except KeyboardInterrupt:
        log('\n')
        log('Keyboard interrupt: exiting.\n')
        return 1