def flush_systemd_dns_cache(): # If the user is using systemd-resolve for DNS resolution, it is # possible for the request to go through systemd-resolve before we # see it...and it may use a cached result instead of sending a # request that we can intercept. When sshuttle starts and stops, # this means that we should clear the cache! # # The command to do this was named systemd-resolve, but changed to # resolvectl in systemd 239. # https://github.com/systemd/systemd/blob/f8eb41003df1a4eab59ff9bec67b2787c9368dbd/NEWS#L3816 p = None if helpers.which("resolvectl"): debug2("Flushing systemd's DNS resolver cache: " "resolvectl flush-caches") p = ssubprocess.Popen(["resolvectl", "flush-caches"], stdout=ssubprocess.PIPE, env=helpers.get_env()) elif helpers.which("systemd-resolve"): debug2("Flushing systemd's DNS resolver cache: " "systemd-resolve --flush-caches") p = ssubprocess.Popen(["systemd-resolve", "--flush-caches"], stdout=ssubprocess.PIPE, env=helpers.get_env()) if p: # Wait so flush is finished and process doesn't show up as defunct. rv = p.wait() if rv != 0: log("Received non-zero return code %d when flushing DNS resolver " "cache." % rv)
def ipfw(*args): argv = ['ipfw', '-q'] + list(args) debug1('>> %s' % ' '.join(argv)) rv = ssubprocess.call(argv, env=get_env()) # No env: No output. (Or error that won't be parsed.) if rv: raise Fatal('%r returned %d' % (argv, rv))
def _check_smb(hostname): return global _smb_ok if not _smb_ok: return debug2(' > smb: %s' % hostname) argv = ['smbclient', '-U', '%', '-L', hostname] try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null, env=get_env()) lines = p.stdout.readlines() p.wait() except OSError: _, e = sys.exc_info()[:2] log('%r failed: %r' % (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' % 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' % (workgroup, hostname)) check_host(hostname) check_workgroup(workgroup) if lines: assert (0)
def nft(family, table, action, *args): if family in (socket.AF_INET, socket.AF_INET6): argv = ['nft', action, 'inet', table] + list(args) else: raise Exception('Unsupported family "%s"' % family_to_string(family)) debug1('%s' % ' '.join(argv)) rv = ssubprocess.call(argv, env=get_env()) if rv: raise Fatal('%r returned %d' % (argv, rv))
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' % ' '.join(argv)) rv = ssubprocess.call(argv, env=get_env()) if rv: raise Fatal('%r returned %d' % (argv, rv))
def pfctl(args, stdin=None): argv = ['pfctl'] + shlex.split(args) debug1('>> %s' % ' '.join(argv)) p = ssubprocess.Popen(argv, stdin=ssubprocess.PIPE, stdout=ssubprocess.PIPE, stderr=ssubprocess.PIPE, env=get_env()) o = p.communicate(stdin) if p.returncode: raise Fatal('%r returned %d' % (argv, p.returncode)) return o
def flush_systemd_dns_cache(): # If the user is using systemd-resolve for DNS resolution, it is # possible for the request to go through systemd-resolve before we # see it...and it may use a cached result instead of sending a # request that we can intercept. When sshuttle starts and stops, # this means that we should clear the cache! # # The command to do this was named systemd-resolve, but changed to # resolvectl in systemd 239. # https://github.com/systemd/systemd/blob/f8eb41003df1a4eab59ff9bec67b2787c9368dbd/NEWS#L3816 if helpers.which("resolvectl"): debug2("Flushing systemd's DNS resolver cache: " "resolvectl flush-caches") ssubprocess.Popen(["resolvectl", "flush-caches"], stdout=ssubprocess.PIPE, env=helpers.get_env()) elif helpers.which("systemd-resolve"): debug2("Flushing systemd's DNS resolver cache: " "systemd-resolve --flush-caches") ssubprocess.Popen(["systemd-resolve", "--flush-caches"], stdout=ssubprocess.PIPE, env=helpers.get_env())
def _fill_oldctls(prefix): argv = ['sysctl', prefix] p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=get_env()) for line in p.stdout: line = line.decode() assert (line[-1] == '\n') (k, v) = line[:-1].split(': ', 1) _oldctls[k] = v.strip() rv = p.wait() if rv: raise Fatal('%r returned %d' % (argv, rv)) if not line: raise Fatal('%r returned no data' % (argv, ))
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'] try: output = ssubprocess.check_output(argv, env=get_env()) for line in output.decode('ASCII').split('\n'): if line.startswith('Chain %s ' % name): return True except ssubprocess.CalledProcessError as e: raise Fatal('%r returned %d' % (argv, e.returncode))
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 pfctl(args, stdin=None): argv = ['pfctl'] + shlex.split(args) debug1('>> %s' % ' '.join(argv)) p = ssubprocess.Popen(argv, stdin=ssubprocess.PIPE, stdout=ssubprocess.PIPE, stderr=ssubprocess.PIPE, env=get_env()) o = p.communicate(stdin) if p.returncode: log('%r returned %d, stdout and stderr follows: ' % (argv, p.returncode)) log("stdout:\n%s" % o[0].decode("ascii")) log("stderr:\n%s" % o[1].decode("ascii")) raise Fatal('%r returned %d' % (argv, p.returncode)) return o
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 _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 _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 # The same IPs may appear multiple times. Consolidate them so the # debug message doesn't print the same IP repeatedly. ip_list = [] for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content): if ip not in ip_list: ip_list.append(ip) for ip in sorted(ip_list): debug3('< %s' % ip) check_host(ip)
def _sysctl_set(name, val): argv = ['sysctl', '-w', '%s=%s' % (name, val)] debug1('>> %s' % ' '.join(argv)) return ssubprocess.call(argv, stdout=open(os.devnull, 'w'), env=get_env())
def enable(self): returncode = ssubprocess.call(['kldload', 'pf'], env=get_env()) # No env: No output. super(FreeBsd, self).enable() if returncode == 0: _pf_context['loaded_by_sshuttle'] = True
def ipfw_noexit(*args): argv = ['ipfw', '-q'] + list(args) debug1('>> %s' % ' '.join(argv)) ssubprocess.call(argv, env=get_env())
def disable(self, anchor): super(FreeBsd, self).disable(anchor) if _pf_context['loaded_by_sshuttle'] and \ _pf_context['started_by_sshuttle'] == 0: ssubprocess.call(['kldunload', 'pf'], env=get_env())
def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl, mock_subprocess_call): mock_pfctl.side_effect = pfctl method = get_method('pf') assert method.name == 'pf' method.setup_firewall( 1024, 1026, [(AF_INET6, u'2404:6800:4004:80c::33')], AF_INET6, [(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000), (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)], False, None) assert mock_pfctl.mock_calls == [ call('-s all'), call( '-a sshuttle6-1024 -f /dev/stdin', b'table <dns_servers> {2404:6800:4004:80c::33}\n' b'rdr pass on lo0 inet6 proto tcp from ! ::1 to ' b'2404:6800:4004:80c::/64 port 8000:9000 -> ::1 port 1024\n' b'rdr pass on lo0 inet6 proto udp ' b'to <dns_servers> port 53 -> ::1 port 1026\n' b'pass out route-to lo0 inet6 proto tcp to ' b'2404:6800:4004:80c::/64 port 8000:9000 keep state\n' b'pass out inet6 proto tcp to ' b'2404:6800:4004:80c::101f/128 port 8080:8080\n' b'pass out route-to lo0 inet6 proto udp ' b'to <dns_servers> port 53 keep state\n'), call('-e'), ] assert call(['kldload', 'pf'], env=get_env()) in \ mock_subprocess_call.mock_calls mock_pf_get_dev.reset_mock() mock_ioctl.reset_mock() mock_pfctl.reset_mock() with pytest.raises(Exception) as excinfo: method.setup_firewall(1025, 1027, [(AF_INET, u'1.2.3.33')], AF_INET, [(AF_INET, 24, False, u'1.2.3.0', 0, 0), (AF_INET, 32, True, u'1.2.3.66', 80, 80)], True, None) assert str(excinfo.value) == 'UDP not supported by pf method_name' assert mock_pf_get_dev.mock_calls == [] assert mock_ioctl.mock_calls == [] assert mock_pfctl.mock_calls == [] method.setup_firewall(1025, 1027, [(AF_INET, u'1.2.3.33')], AF_INET, [(AF_INET, 24, False, u'1.2.3.0', 0, 0), (AF_INET, 32, True, u'1.2.3.66', 80, 80)], False, None) assert mock_ioctl.mock_calls == [ call(mock_pf_get_dev(), 0xC4704433, ANY), call(mock_pf_get_dev(), 0xCBE0441A, ANY), call(mock_pf_get_dev(), 0xCBE0441A, ANY), call(mock_pf_get_dev(), 0xC4704433, ANY), call(mock_pf_get_dev(), 0xCBE0441A, ANY), call(mock_pf_get_dev(), 0xCBE0441A, ANY), ] assert mock_pfctl.mock_calls == [ call('-s all'), call( '-a sshuttle-1025 -f /dev/stdin', b'table <dns_servers> {1.2.3.33}\n' b'rdr pass on lo0 inet proto tcp from ! 127.0.0.1 ' b'to 1.2.3.0/24 -> 127.0.0.1 port 1025\n' b'rdr pass on lo0 inet proto udp ' b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n' b'pass out route-to lo0 inet proto tcp to 1.2.3.0/24 keep state\n' b'pass out inet proto tcp to 1.2.3.66/32 port 80:80\n' b'pass out route-to lo0 inet proto udp ' b'to <dns_servers> port 53 keep state\n'), call('-e'), ] mock_pf_get_dev.reset_mock() mock_ioctl.reset_mock() mock_pfctl.reset_mock() method.restore_firewall(1025, AF_INET, False, None) method.restore_firewall(1024, AF_INET6, False, None) assert mock_ioctl.mock_calls == [] assert mock_pfctl.mock_calls == [ call('-a sshuttle-1025 -F all'), call('-a sshuttle6-1024 -F all'), call("-d"), ] mock_pf_get_dev.reset_mock() mock_pfctl.reset_mock() mock_ioctl.reset_mock()