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])
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,))
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]
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)
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
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)
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)
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))
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)
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)
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))
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
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))
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
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))
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 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))
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
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
def check(self): rv = self.p.poll() if rv: raise Fatal('%r returned %d' % (self.argv, rv))
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("/")
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:
def done(self): self.pfile.close() rv = self.p.wait() if rv: raise Fatal('cleanup: %r returned %d' % (self.argv, rv))
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
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)
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))