Exemple #1
0
 def recv_udp(listener, bufsize):
     debug3('Accept UDP using socket_ext recvmsg.\n')
     srcip, data, adata, flags = listener.recvmsg((bufsize, ),
                                                  socket.CMSG_SPACE(24))
     dstip = None
     family = None
     for a in adata:
         if a.cmsg_level == socket.SOL_IP and a.cmsg_type == IP_ORIGDSTADDR:
             family, port = struct.unpack('=HH', a.cmsg_data[0:4])
             port = socket.htons(port)
             if family == socket.AF_INET:
                 start = 4
                 length = 4
             else:
                 raise Fatal("Unsupported socket type '%s'" % family)
             ip = socket.inet_ntop(family,
                                   a.cmsg_data[start:start + length])
             dstip = (ip, port)
             break
         elif a.cmsg_level == SOL_IPV6 and a.cmsg_type == IPV6_ORIGDSTADDR:
             family, port = struct.unpack('=HH', a.cmsg_data[0:4])
             port = socket.htons(port)
             if family == socket.AF_INET6:
                 start = 8
                 length = 16
             else:
                 raise Fatal("Unsupported socket type '%s'" % family)
             ip = socket.inet_ntop(family,
                                   a.cmsg_data[start:start + length])
             dstip = (ip, port)
             break
     return (srcip, dstip, data[0])
Exemple #2
0
def _fill_oldctls(prefix):
    argv = ['sysctl', prefix]
    p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
    for line in p.stdout:
        assert(line[-1] == '\n')
        (k, v) = line[:-1].split(': ', 1)
        _oldctls[k] = v
    rv = p.wait()
    if rv:
        raise Fatal('%r returned %d' % (argv, rv))
    if not line:
        raise Fatal('%r returned no data' % (argv,))
Exemple #3
0
class FirewallClient:
    def __init__(self, port_v6, port_v4, subnets_include, subnets_exclude,
                 dnsport_v6, dnsport_v4, method, udp):
        self.auto_nets = []
        self.subnets_include = subnets_include
        self.subnets_exclude = subnets_exclude
        argvbase = ([sys.argv[1], sys.argv[0], sys.argv[1]] + ['-v'] *
                    (helpers.verbose or 0) + [
                        '--firewall',
                        str(port_v6),
                        str(port_v4),
                        str(dnsport_v6),
                        str(dnsport_v4), method,
                        str(int(udp))
                    ])
        if ssyslog._p:
            argvbase += ['--syslog']
        argv_tries = [['sudo', '-p', '[local sudo] Password: '******'su', '-c', ' '.join(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, e:
                pass
        self.argv = argv
        s1.close()
        self.pfile = s2.makefile('wb+')
        if e:
            log('Spawning firewall manager: %r\n' % self.argv)
            raise Fatal(e)
        line = self.pfile.readline()
        self.check()
        if line[0:5] != 'READY':
            raise Fatal('%r expected READY, got %r' % (self.argv, line))
        self.method = line[6:-1]
Exemple #4
0
def parse_subnet6(s):
    m = re.match(r'(?:([a-fA-F\d:]+))?(?:/(\d+))?$', s)
    if not m:
        raise Fatal('%r is not a valid IP subnet format' % s)
    (net, width) = m.groups()
    if width is None:
        width = 128
    else:
        width = int(width)
    if width > 128:
        raise Fatal('*/%d is greater than the maximum of 128' % width)
    return (socket.AF_INET6, net, width)
Exemple #5
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 #6
0
def parse_ipport4(s):
    s = str(s)
    m = re.match(r'(?:(\d+)\.(\d+)\.(\d+)\.(\d+))?(?::)?(?:(\d+))?$', s)
    if not m:
        raise Fatal('%r is not a valid IP:port format' % s)
    (a, b, c, d, port) = m.groups()
    (a, b, c, d, port) = (int(a or 0), int(b or 0), int(c or 0), int(d or 0),
                          int(port or 0))
    if a > 255 or b > 255 or c > 255 or d > 255:
        raise Fatal('%d.%d.%d.%d has numbers > 255' % (a, b, c, d))
    if port > 65535:
        raise Fatal('*:%d is greater than the maximum of 65535' % port)
    if a is None:
        a = b = c = d = 0
    return ('%d.%d.%d.%d' % (a, b, c, d), port)
Exemple #7
0
def parse_subnet4(s):
    m = re.match(r'(\d+)(?:\.(\d+)\.(\d+)\.(\d+))?(?:/(\d+))?$', s)
    if not m:
        raise Fatal('%r is not a valid IP subnet format' % s)
    (a, b, c, d, width) = m.groups()
    (a, b, c, d) = (int(a or 0), int(b or 0), int(c or 0), int(d or 0))
    if width is None:
        width = 32
    else:
        width = int(width)
    if a > 255 or b > 255 or c > 255 or d > 255:
        raise Fatal('%d.%d.%d.%d has numbers > 255' % (a, b, c, d))
    if width > 32:
        raise Fatal('*/%d is greater than the maximum of 32' % width)
    return (socket.AF_INET, '%d.%d.%d.%d' % (a, b, c, d), width)
Exemple #8
0
def pfctl(*args):
    # TODO: pretty sure this doesn't work. Need to research how pf works.
    argv = ['pfctl', '-q', ] + list(args)
    debug1('>> %s\n' % ' '.join(argv))
    rv = ssubprocess.call(argv)
    if rv:
        raise Fatal('%r returned %d' % (argv, rv))
Exemple #9
0
def runonce(handlers, mux):
    r = []
    w = []
    x = []
    to_remove = filter(lambda s: not s.ok, handlers)
    for h in to_remove:
        handlers.remove(h)

    for s in handlers:
        s.pre_select(r, w, x)
    debug2('Waiting: %d r=%r w=%r x=%r (fullness=%d/%d)\n'
           % (len(handlers), _fds(r), _fds(w), _fds(x),
               mux.fullness, mux.too_full))
    (r, w, x) = select.select(r, w, x)
    debug2('  Ready: %d r=%r w=%r x=%r\n'
           % (len(handlers), _fds(r), _fds(w), _fds(x)))
    ready = r + w + x
    did = {}
    for h in handlers:
        for s in h.socks:
            if s in ready:
                h.callback()
                did[s] = 1
    for s in ready:
        if not s in did:
            raise Fatal('socket %r was not used by any handler' % s)
Exemple #10
0
def parse_ipport6(s):
    s = str(s)
    m = re.match(r'(?:\[([^]]*)])?(?::)?(?:(\d+))?$', s)
    if not m:
        raise Fatal('%s is not a valid IP:port format' % s)
    (ip, port) = m.groups()
    (ip, port) = (ip or '::', int(port or 0))
    return (ip, port)
Exemple #11
0
def check_daemon(pidfile):
    global _pidname
    _pidname = os.path.abspath(pidfile)
    try:
        oldpid = open(_pidname).read(1024)
    except IOError, e:
        if e.errno == errno.ENOENT:
            return  # no pidfile, ok
        else:
            raise Fatal("can't read %s: %s" % (_pidname, e))
Exemple #12
0
 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
Exemple #13
0
def _ipt(family, table, *args):
    if family == socket.AF_INET6:
        argv = ['ip6tables', '-t', table] + list(args)
    elif family == socket.AF_INET:
        argv = ['iptables', '-t', table] + list(args)
    else:
        raise Exception('Unsupported family "%s"' % family_to_string(family))
    debug1('>> %s\n' % ' '.join(argv))
    rv = ssubprocess.call(argv)
    if rv:
        raise Fatal('%r returned %d' % (argv, rv))
Exemple #14
0
def pfctl(args, stdin = None):
    argv = ['pfctl'] + list(args.split(" "))
    debug1('>> %s\n' % ' '.join(argv))

    p = ssubprocess.Popen(argv, stdin = ssubprocess.PIPE, 
                                stdout = ssubprocess.PIPE, 
                                stderr = ssubprocess.PIPE)
    o = p.communicate(stdin)
    if p.returncode:
        raise Fatal('%r returned %d' % (argv, p.returncode))

    return o
Exemple #15
0
 def start(self):
     self.pfile.write('ROUTES\n')
     for (family, ip, width) in self.subnets_include + self.auto_nets:
         self.pfile.write('%d,%d,0,%s\n' % (family, width, ip))
     for (family, ip, width) in self.subnets_exclude:
         self.pfile.write('%d,%d,1,%s\n' % (family, width, ip))
     self.pfile.write('GO\n')
     self.pfile.flush()
     line = self.pfile.readline()
     self.check()
     if line != 'STARTED\n':
         raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
Exemple #16
0
 def hostwatch_ready():
     assert (hw.pid)
     content = hw.sock.recv(4096)
     if content:
         lines = (hw.leftover + content).split('\n')
         if lines[-1]:
             # no terminating newline: entry isn't complete yet!
             hw.leftover = lines.pop()
             lines.append('')
         else:
             hw.leftover = ''
         mux.send(0, ssnet.CMD_HOST_LIST, '\n'.join(lines))
     else:
         raise Fatal('hostwatch process died')
Exemple #17
0
def ipt_chain_exists(family, table, name):
    if family == socket.AF_INET6:
        cmd = 'ip6tables'
    elif family == socket.AF_INET:
        cmd = 'iptables'
    else:
        raise Exception('Unsupported family "%s"' % family_to_string(family))
    argv = [cmd, '-t', table, '-nL']
    p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
    for line in p.stdout:
        if line.startswith('Chain %s ' % name):
            return True
    rv = p.wait()
    if rv:
        raise Fatal('%r returned %d' % (argv, rv))
Exemple #18
0
def parse_subnet_file(s):
    try:
        handle = open(s, 'r')
    except OSError:
        raise Fatal('Unable to open subnet file: %s' % s)

    raw_config_lines = handle.readlines()
    config_lines = []
    for line_no, line in enumerate(raw_config_lines):
        line = line.strip()
        if len(line) == 0:
            continue
        if line[0] == '#':
            continue
        config_lines.append(line)

    return config_lines
Exemple #19
0
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, python,
          latency_control, dns_listener, method, seed_hosts, auto_nets, syslog,
          daemon):
    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, method=method))
    except socket.error, e:
        if e.args[0] == errno.EPIPE:
            raise Fatal("failed to establish ssh session (1)")
        else:
            raise
Exemple #20
0
 def check(self):
     rv = self.p.poll()
     if rv:
         raise Fatal('%r returned %d' % (self.argv, rv))
Exemple #21
0
        return  # invalid pidfile, ok
    oldpid = int(oldpid.strip() or 0)
    if oldpid <= 0:
        os.unlink(_pidname)
        return  # invalid pidfile, ok
    try:
        os.kill(oldpid, 0)
    except OSError, e:
        if e.errno == errno.ESRCH:
            os.unlink(_pidname)
            return  # outdated pidfile, ok
        elif e.errno == errno.EPERM:
            pass
        else:
            raise
    raise Fatal("%s: sshuttle is already running (pid=%d)" %
                (_pidname, oldpid))


def daemonize():
    if os.fork():
        os._exit(0)
    os.setsid()
    if os.fork():
        os._exit(0)

    outfd = os.open(_pidname, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0666)
    try:
        os.write(outfd, '%d\n' % os.getpid())
    finally:
        os.close(outfd)
    os.chdir("/")
Exemple #22
0
def main(port_v6, port_v4, dnsport_v6, dnsport_v4, method, udp, syslog):
    assert(port_v6 >= 0)
    assert(port_v6 <= 65535)
    assert(port_v4 >= 0)
    assert(port_v4 <= 65535)
    assert(dnsport_v6 >= 0)
    assert(dnsport_v6 <= 65535)
    assert(dnsport_v4 >= 0)
    assert(dnsport_v4 <= 65535)

    if os.getuid() != 0:
        raise Fatal('you must be root (or enable su/sudo) to set the firewall')

    if method == "auto":
        if program_exists('ipfw'):
            method = "ipfw"
        elif program_exists('iptables'):
            method = "nat"
        elif program_exists('pfctl'):
            method = "pf"
        else:
            raise Fatal("can't find either ipfw, iptables or pfctl; check your PATH")

    if method == "nat":
        do_it = do_iptables_nat
    elif method == "tproxy":
        do_it = do_iptables_tproxy
    elif method == "ipfw":
        do_it = do_ipfw
    elif method == "pf":
        do_it = do_pf
    else:
        raise Exception('Unknown method "%s"' % method)

    # because of limitations of the 'su' command, the *real* stdin/stdout
    # are both attached to stdout initially.  Clone stdout into stdin so we
    # can read from it.
    os.dup2(1, 0)

    if syslog:
        ssyslog.start_syslog()
        ssyslog.stderr_to_syslog()

    debug1('firewall manager ready method %s.\n' % method)
    sys.stdout.write('READY %s\n' % method)
    sys.stdout.flush()

    # don't disappear if our controlling terminal or stdout/stderr
    # disappears; we still have to clean up.
    signal.signal(signal.SIGHUP, signal.SIG_IGN)
    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
    signal.signal(signal.SIGTERM, signal.SIG_IGN)
    signal.signal(signal.SIGINT, signal.SIG_IGN)

    # ctrl-c shouldn't be passed along to me.  When the main sshuttle dies,
    # I'll die automatically.
    os.setsid()

    # we wait until we get some input before creating the rules.  That way,
    # sshuttle can launch us as early as possible (and get sudo password
    # authentication as early in the startup process as possible).
    line = sys.stdin.readline(128)
    if not line:
        return  # parent died; nothing to do

    subnets = []
    if line != 'ROUTES\n':
        raise Fatal('firewall: expected ROUTES but got %r' % line)
    while 1:
        line = sys.stdin.readline(128)
        if not line:
            raise Fatal('firewall: expected route but got %r' % line)
        elif line == 'GO\n':
            break
        try:
            (family, width, exclude, ip) = line.strip().split(',', 3)
        except:
            raise Fatal('firewall: expected route or GO but got %r' % line)
        subnets.append((int(family), int(width), bool(int(exclude)), ip))

    try:
        if line:
            debug1('firewall manager: starting transproxy.\n')

            subnets_v6 = filter(lambda i: i[0] == socket.AF_INET6, subnets)
            if port_v6:
                do_wait = do_it(
                    port_v6, dnsport_v6, socket.AF_INET6, subnets_v6, udp)
            elif len(subnets_v6) > 0:
                debug1("IPv6 subnets defined but IPv6 disabled\n")

            subnets_v4 = filter(lambda i: i[0] == socket.AF_INET, subnets)
            if port_v4:
                do_wait = do_it(
                    port_v4, dnsport_v4, socket.AF_INET, subnets_v4, udp)
            elif len(subnets_v4) > 0:
                debug1('IPv4 subnets defined but IPv4 disabled\n')

            sys.stdout.write('STARTED\n')

        try:
            sys.stdout.flush()
        except IOError:
            # the parent process died for some reason; he's surely been loud
            # enough, so no reason to report another error
            return

        # Now we wait until EOF or any other kind of exception.  We need
        # to stay running so that we don't need a *second* password
        # authentication at shutdown time - that cleanup is important!
        while 1:
            if do_wait:
                do_wait()
            line = sys.stdin.readline(128)
            if line.startswith('HOST '):
                (name, ip) = line[5:].strip().split(',', 1)
                hostmap[name] = ip
                rewrite_etc_hosts(port_v6 or port_v4)
            elif line.startswith('QUERY_PF_NAT '):
                try:
                    dst = pf_query_nat(*(line[13:].split(',')))
                    sys.stdout.write('QUERY_PF_NAT_SUCCESS %s,%r\n' % dst)
                except IOError, e:
                    sys.stdout.write('QUERY_PF_NAT_FAILURE %s\n' % e)

                sys.stdout.flush()
            elif line:
                raise Fatal('expected EOF, got %r' % line)
            else:
Exemple #23
0
 def done(self):
     self.pfile.close()
     rv = self.p.wait()
     if rv:
         raise Fatal('cleanup: %r returned %d' % (self.argv, rv))
Exemple #24
0
def main():
    if helpers.verbose >= 1:
        helpers.logprefix = ' s: '
    else:
        helpers.logprefix = 'server: '
    assert latency_control is not None
    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, routepkt)

    hw = Hostwatch()
    hw.leftover = ''

    def hostwatch_ready():
        assert (hw.pid)
        content = hw.sock.recv(4096)
        if content:
            lines = (hw.leftover + content).split('\n')
            if lines[-1]:
                # no terminating newline: entry isn't complete yet!
                hw.leftover = lines.pop()
                lines.append('')
            else:
                hw.leftover = ''
            mux.send(0, ssnet.CMD_HOST_LIST, '\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.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()
        mux.callback()

        if dnshandlers:
            now = time.time()
            for channel, h in dnshandlers.items():
                if h.timeout < now or not h.ok:
                    debug3('expiring dnsreqs channel=%d\n' % channel)
                    del dnshandlers[channel]
                    h.ok = False
        if udphandlers:
            for channel, h in udphandlers.items():
                if not h.ok:
                    debug3('expiring UDP channel=%d\n' % channel)
                    del udphandlers[channel]
                    h.ok = False
Exemple #25
0
 def fill(self):
     self.rsock.setblocking(False)
     try:
         b = _nb_clean(os.read, self.rsock.fileno(), 32768)
     except OSError, e:
         raise Fatal('other end: %r' % e)
Exemple #26
0
def ipfw(*args):
    argv = ['ipfw', '-q'] + list(args)
    debug1('>> %s\n' % ' '.join(argv))
    rv = ssubprocess.call(argv)
    if rv:
        raise Fatal('%r returned %d' % (argv, rv))