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)
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)
def try_send(self): if self.tries >= 3: return self.tries += 1 family, peer = resolvconf_random_nameserver() sock = socket.socket(family, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42) sock.connect((peer, 53)) self.peers[sock] = peer debug2('DNS: sending to %r (try %d)\n' % (peer, self.tries)) try: sock.send(self.request) self.socks.append(sock) except socket.error: _, e = sys.exc_info()[:2] if e.args[0] in ssnet.NET_ERRS: # might have been spurious; try again. # Note: these errors sometimes are reported by recv(), # and sometimes by send(). We have to catch both. debug2('DNS send to %r: %s\n' % (peer, e)) self.try_send() return else: log('DNS send to %r: %s\n' % (peer, e)) return
def _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
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
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)
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)
def send(self, dstip, data): debug2('UDP: sending to %r port %d\n' % dstip) try: self.sock.sendto(data, dstip) except socket.error: _, e = sys.exc_info()[:2] log('UDP send to %r port %d: %s\n' % (dstip[0], dstip[1], e)) return
def 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()
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
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)
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
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)
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)
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
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)
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")
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)
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)
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)
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)
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)
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
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)
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))
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)
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 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)
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)
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)
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)
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)
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)
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
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
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)
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)
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
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))
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
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
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)
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
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))
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)
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
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
def nonfatal(func, *args): try: func(*args) except Fatal as e: log('error: %s\n' % e)
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)
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)
def got_signal(signum, frame): log('exiting on signal %d\n' % signum) sys.exit(1)
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
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()
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
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()
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-------'
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