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 _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 connect_dst(family, ip, port): debug2('Connecting to %s:%d\n' % (ip, port)) outsock = socket.socket(family) outsock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42) return SockWrapper(outsock, outsock, connect_to=(ip, port), peername='%s:%d' % (ip, port))
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 runonce(handlers, mux): r = [] w = [] x = [] to_remove = [s for s in handlers if not s.ok] 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(s) did[s] = 1 for s in ready: if s not in did: raise Fatal('socket %r was not used by any handler' % s)
def __init__(self, mux, channel): SockWrapper.__init__(self, mux.rsock, mux.wsock) self.mux = mux self.channel = channel self.mux.channels[channel] = self.got_packet self.socks = [] debug2('new channel: %d\n' % channel)
def nowrite(self): if not self.shut_write: debug2('%r: done writing\n' % self) self.shut_write = True try: self.wsock.shutdown(SHUT_WR) except socket.error as e: self.seterr('nowrite: %s' % e)
def _check_revdns(ip): debug2(' > rev: %s\n' % ip) try: r = socket.gethostbyaddr(ip) debug3('< %s\n' % r[0]) check_host(r[0]) found_host(r[0], ip) except socket.herror: pass
def send(self, channel, cmd, data): assert isinstance(data, bytes) assert len(data) <= 65535 p = struct.pack('!ccHHH', b'S', b'S', channel, cmd, len(data)) + data self.outbuf.append(p) debug2(' > channel=%d cmd=%s len=%d (fullness=%d)\n' % (channel, cmd_to_name.get(cmd, hex(cmd)), len(data), self.fullness)) self.fullness += len(data)
def flush(self): self.wsock.setblocking(False) if self.outbuf and self.outbuf[0]: wrote = _nb_clean(os.write, self.wsock.fileno(), self.outbuf[0]) debug2('mux wrote: %r/%d\n' % (wrote, len(self.outbuf[0]))) if wrote: self.outbuf[0] = self.outbuf[0][wrote:] while self.outbuf and not self.outbuf[0]: self.outbuf[0:1] = []
def print_listening(self, what): if self.v6: listenip = self.v6.getsockname() debug1('%s listening on %r.\n' % (what, listenip)) debug2('%s listening with %r.\n' % (what, self.v6)) if self.v4: listenip = self.v4.getsockname() debug1('%s listening on %r.\n' % (what, listenip)) debug2('%s listening with %r.\n' % (what, self.v4))
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 _check_dns(hostname): debug2(' > dns: %s\n' % hostname) try: ip = socket.gethostbyname(hostname) debug3('< %s\n' % ip) check_host(ip) found_host(hostname, ip) except socket.gaierror: pass
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 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 _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 _check_etc_hosts(): debug2(" > hosts\n") for line in open("/etc/hosts"): line = re.sub(r"#.*", "", line) words = line.strip().split() if not words: continue ip = words[0] names = words[1:] if _is_ip(ip): debug3("< %s %r\n" % (ip, names)) for n in names: check_host(n) found_host(n, 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 _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_etc_hosts(): debug2(' > hosts\n') for line in open('/etc/hosts'): line = re.sub(r'#.*', '', line) words = line.strip().split() if not words: continue ip = words[0] names = words[1:] if _is_ip(ip): debug3('< %s %r\n' % (ip, names)) for n in names: check_host(n) found_host(n, ip)
def get_tcp_dstip(self, sock): pfile = self.firewall.pfile peer = sock.getpeername() proxy = sock.getsockname() argv = (sock.family, socket.IPPROTO_TCP, peer[0], peer[1], proxy[0], proxy[1]) pfile.write("QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % argv) pfile.flush() line = pfile.readline() debug2("QUERY_PF_NAT %d,%d,%s,%d,%s,%d" % argv + " > " + line) if line.startswith("QUERY_PF_NAT_SUCCESS "): (ip, port) = line[21:].split(",") return (ip, int(port)) return sock.getsockname()
def get_tcp_dstip(self, sock): pfile = self.firewall.pfile peer = sock.getpeername() proxy = sock.getsockname() argv = (sock.family, socket.IPPROTO_TCP, peer[0].encode("ASCII"), peer[1], proxy[0].encode("ASCII"), proxy[1]) out_line = b"QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % argv pfile.write(out_line) pfile.flush() in_line = pfile.readline() debug2(out_line.decode("ASCII") + " > " + in_line.decode("ASCII")) if in_line.startswith(b"QUERY_PF_NAT_SUCCESS "): (ip, port) = in_line[21:].split(b",") return (ip.decode("ASCII"), int(port)) return sock.getsockname()
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 onroutes(routestr): if auto_nets: for line in routestr.strip().split(b'\n'): if not line: continue (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, 0, 0)) # 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()
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 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 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()
def is_supported(self): if which("iptables") and which("ip6tables"): return True debug2("tproxy method not supported because 'iptables' " "or 'ip6tables' commands are missing.\n") return False
def connect(ssh_cmd, rhostport, python, stderr, options): username, password, port, host = parse_hostport(rhostport) if username: rhost = "{}@{}".format(username, host) else: rhost = host z = zlib.compressobj(1) content = get_module_source('sshuttle.assembler') optdata = ''.join("%s=%r\n" % (k, v) for (k, v) in list(options.items())) optdata = optdata.encode("UTF8") content2 = (empackage(z, 'sshuttle') + empackage(z, 'sshuttle.cmdline_options', optdata) + empackage(z, 'sshuttle.helpers') + empackage(z, 'sshuttle.ssnet') + empackage(z, 'sshuttle.hostwatch') + empackage(z, 'sshuttle.server') + b"\n") # If the exec() program calls sys.exit(), it should exit python # and the sys.exit(98) call won't be reached (so we try to only # exit that way in the server). However, if the code that we # exec() simply returns from main, then we will return from # exec(). If the server's python process dies, it should stop # executing and also won't reach sys.exit(98). # # So, we shouldn't reach sys.exit(98) and we certainly shouldn't # reach it immediately after trying to start the server. pyscript = r""" import sys, os; verbosity=%d; sys.stdin = os.fdopen(0, "rb"); exec(compile(sys.stdin.read(%d), "assembler.py", "exec")); sys.exit(98); """ % (helpers.verbose or 0, len(content)) pyscript = re.sub(r'\s+', ' ', pyscript.strip()) if not rhost: # ignore the --python argument when running locally; we already know # which python version works. argv = [sys.executable, '-c', pyscript] else: if ssh_cmd: sshl = shlex.split(ssh_cmd) else: sshl = ['ssh'] if port is not None: portl = ["-p", str(port)] else: portl = [] if python: pycmd = "'%s' -c '%s'" % (python, pyscript) else: # By default, we run the following code in a shell. # However, with restricted shells and other unusual # situations, there can be trouble. See the RESTRICTED # SHELL section in "man bash" for more information. The # code makes many assumptions: # # (1) That /bin/sh exists and that we can call it. # Restricted shells often do *not* allow you to run # programs specified with an absolute path like /bin/sh. # Either way, if there is trouble with this, it should # return error code 127. # # (2) python3 or python exists in the PATH and is # executable. If they aren't, then exec won't work (see (4) # below). # # (3) In /bin/sh, that we can redirect stderr in order to # hide the version that "python3 -V" might print (some # restricted shells don't allow redirection, see # RESTRICTED SHELL section in 'man bash'). However, if we # are in a restricted shell, we'd likely have trouble with # assumption (1) above. # # (4) The 'exec' command should work except if we failed # to exec python because it doesn't exist or isn't # executable OR if exec isn't allowed (some restricted # shells don't allow exec). If the exec succeeded, it will # not return and not get to the "exit 97" command. If exec # does return, we exit with code 97. # # Specifying the exact python program to run with --python # avoids many of the issues above. However, if # you have a restricted shell on remote, you may only be # able to run python if it is in your PATH (and you can't # run programs specified with an absolute path). In that # case, sshuttle might not work at all since it is not # possible to run python on the remote machine---even if # it is present. pycmd = ("P=python3; $P -V 2>%s || P=python; " "exec \"$P\" -c %s; exit 97") % \ (os.devnull, quote(pyscript)) pycmd = ("/bin/sh -c {}".format(quote(pycmd))) if password is not None: os.environ['SSHPASS'] = str(password) argv = (["sshpass", "-e"] + sshl + portl + [rhost, '--', pycmd]) else: argv = (sshl + portl + [rhost, '--', pycmd]) # Our which() function searches for programs in get_path() # directories (which include PATH). This step isn't strictly # necessary if ssh is already in the user's PATH, but it makes the # error message friendlier if the user incorrectly passes in a # custom ssh command that we cannot find. abs_path = which(argv[0]) if abs_path is None: raise Fatal("Failed to find '%s' in path %s" % (argv[0], get_path())) argv[0] = abs_path (s1, s2) = socket.socketpair() def setup(): # runs in the child process s2.close() s1a, s1b = os.dup(s1.fileno()), os.dup(s1.fileno()) s1.close() debug2('executing: %r' % argv) p = ssubprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup, close_fds=True, stderr=stderr) os.close(s1a) os.close(s1b) s2.sendall(content) s2.sendall(content2) return p, s2
def dns_req(channel, data): debug2('Incoming DNS request channel=%d.\n' % channel) h = DnsProxy(mux, channel, data) handlers.append(h) dnshandlers[channel] = h
def is_supported(self): if which("iptables"): return True debug2("nat method not supported because 'iptables' command " "is missing.") return False
def main(listenip_v6, listenip_v4, ssh_cmd, remotename, python, latency_control, dns, nslist, method_name, seed_hosts, auto_hosts, auto_nets, subnets_include, subnets_exclude, daemon, to_nameserver, pidfile, user, sudo_pythonpath): if not remotename: print("WARNING: You must specify -r/--remote to securely route " "traffic to a remote machine. Running without -r/--remote " "is only recommended for testing.") if daemon: try: check_daemon(pidfile) except Fatal as e: log("%s\n" % e) return 5 debug1('Starting sshuttle proxy (version %s).\n' % __version__) helpers.logprefix = 'c : ' fw = FirewallClient(method_name, sudo_pythonpath) # If --dns is used, store the IP addresses that the client # normally uses for DNS lookups in nslist. The firewall needs to # redirect packets outgoing to this server to the remote host # instead. if dns: nslist += resolvconf_nameservers(True) if to_nameserver is not None: to_nameserver = "%s@%s" % tuple(to_nameserver[1:]) else: # option doesn't make sense if we aren't proxying dns if to_nameserver and len(to_nameserver) > 0: print("WARNING: --to-ns option is ignored because --dns was not " "used.") to_nameserver = None # Get family specific subnet lists. Also, the user may not specify # any subnets if they use --auto-nets. In this case, our subnets # list will be empty and the forwarded subnets will be determined # later by the server. subnets_v4 = [i for i in subnets_include if i[0] == socket.AF_INET] subnets_v6 = [i for i in subnets_include if i[0] == socket.AF_INET6] nslist_v4 = [i for i in nslist if i[0] == socket.AF_INET] nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6] # Get available features from the firewall method avail = fw.method.get_supported_features() # A feature is "required" if the user supplies us parameters which # implies that the feature is needed. required = Features() # Select the default addresses to bind to / listen to. # Assume IPv4 is always available and should always be enabled. If # a method doesn't provide IPv4 support or if we wish to run # ipv6-only, changes to this code are required. assert avail.ipv4 required.ipv4 = True # listenip_v4 contains user specified value or it is set to "auto". if listenip_v4 == "auto": listenip_v4 = ('127.0.0.1', 0) # listenip_v6 is... # None when IPv6 is disabled. # "auto" when listen address is unspecified. # The user specified address if provided by user if listenip_v6 is None: debug1("IPv6 disabled by --disable-ipv6\n") if listenip_v6 == "auto": if avail.ipv6: debug1("IPv6 enabled: Using default IPv6 listen address ::1\n") listenip_v6 = ('::1', 0) else: debug1("IPv6 disabled since it isn't supported by method " "%s.\n" % fw.method.name) listenip_v6 = None # Make final decision about enabling IPv6: required.ipv6 = False if listenip_v6: required.ipv6 = True # If we get here, it is possible that listenip_v6 was user # specified but not supported by the current method. if required.ipv6 and not avail.ipv6: raise Fatal("An IPv6 listen address was supplied, but IPv6 is " "disabled at your request or is unsupported by the %s " "method." % fw.method.name) if user is not None: if getpwnam is None: raise Fatal("Routing by user not available on this system.") try: user = getpwnam(user).pw_uid except KeyError: raise Fatal("User %s does not exist." % user) required.user = False if user is None else True if not required.ipv6 and len(subnets_v6) > 0: print("WARNING: IPv6 subnets were ignored because IPv6 is disabled " "in sshuttle.") subnets_v6 = [] subnets_include = subnets_v4 required.udp = avail.udp # automatically enable UDP if it is available required.dns = len(nslist) > 0 # Remove DNS servers using IPv6. if required.dns: if not required.ipv6 and len(nslist_v6) > 0: print("WARNING: Your system is configured to use an IPv6 DNS " "server but sshuttle is not using IPv6. Therefore DNS " "traffic your system sends to the IPv6 DNS server won't " "be redirected via sshuttle to the remote machine.") nslist_v6 = [] nslist = nslist_v4 if len(nslist) == 0: raise Fatal("Can't redirect DNS traffic since IPv6 is not " "enabled in sshuttle and all of the system DNS " "servers are IPv6.") # If we aren't using IPv6, we can safely ignore excluded IPv6 subnets. if not required.ipv6: orig_len = len(subnets_exclude) subnets_exclude = [ i for i in subnets_exclude if i[0] == socket.AF_INET ] if len(subnets_exclude) < orig_len: print("WARNING: Ignoring one or more excluded IPv6 subnets " "because IPv6 is not enabled.") # This will print error messages if we required a feature that # isn't available by the current method. fw.method.assert_features(required) # display features enabled def feature_status(label, enabled, available): msg = label + ": " if enabled: msg += "on" else: msg += "off " if available: msg += "(available)" else: msg += "(not available with %s method)" % fw.method.name debug1(msg + "\n") debug1("Method: %s\n" % fw.method.name) feature_status("IPv4", required.ipv4, avail.ipv4) feature_status("IPv6", required.ipv6, avail.ipv6) feature_status("UDP ", required.udp, avail.udp) feature_status("DNS ", required.dns, avail.dns) feature_status("User", required.user, avail.user) # Exclude traffic destined to our listen addresses. if required.ipv4 and \ not any(listenip_v4[0] == sex[1] for sex in subnets_v4): subnets_exclude.append((socket.AF_INET, listenip_v4[0], 32, 0, 0)) if required.ipv6 and \ not any(listenip_v6[0] == sex[1] for sex in subnets_v6): subnets_exclude.append((socket.AF_INET6, listenip_v6[0], 128, 0, 0)) # We don't print the IP+port of where we are listening here # because we do that below when we have identified the ports to # listen on. debug1("Subnets to forward through remote host (type, IP, cidr mask " "width, startPort, endPort):\n") for i in subnets_include: debug1(" " + str(i) + "\n") if auto_nets: debug1("NOTE: Additional subnets to forward may be added below by " "--auto-nets.\n") debug1("Subnets to exclude from forwarding:\n") for i in subnets_exclude: debug1(" " + str(i) + "\n") if required.dns: debug1("DNS requests normally directed at these servers will be " "redirected to remote:\n") for i in nslist: debug1(" " + str(i) + "\n") 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) # keep track of failed bindings and used ports to avoid trying to # bind to the same socket address twice in different listeners used_ports = [] # search for free ports and try to bind last_e = None redirectport_v6 = 0 redirectport_v4 = 0 bound = False for port in ports: debug2('Trying to bind redirector on port %d\n' % 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 used_ports.append(port) break except socket.error as e: if e.errno == errno.EADDRINUSE: last_e = e used_ports.append(port) else: raise e 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 ports = range(12300, 9000, -1) for port in ports: debug2('Trying to bind DNS redirector on port %d\n' % port) if port in used_ports: continue 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 used_ports.append(port) break except socket.error as e: if e.errno == errno.EADDRINUSE: last_e = e used_ports.append(port) else: raise e 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 subnets_v6: assert required.ipv6 if redirectport_v6 == 0: raise Fatal("IPv6 subnets defined but not listening") if nslist_v6: assert required.dns assert required.ipv6 if dnsport_v6 == 0: raise Fatal("IPv6 ns servers defined but not listening") if subnets_v4: if redirectport_v4 == 0: raise Fatal("IPv4 subnets defined but not listening") if nslist_v4: 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, user) # start the client process try: return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, python, latency_control, dns_listener, seed_hosts, auto_hosts, auto_nets, daemon, to_nameserver) finally: try: if daemon: # it's not our child anymore; can't waitpid fw.p.returncode = 0 fw.done() sdnotify.send(sdnotify.stop()) finally: if daemon: daemon_cleanup()
def connect(ssh_cmd, rhostport, python, stderr, options): username, password, port, host = parse_hostport(rhostport) if username: rhost = "{}@{}".format(username, host) else: rhost = host z = zlib.compressobj(1) content = get_module_source('sshuttle.assembler') optdata = ''.join("%s=%r\n" % (k, v) for (k, v) in list(options.items())) optdata = optdata.encode("UTF8") content2 = (empackage(z, 'sshuttle') + empackage(z, 'sshuttle.cmdline_options', optdata) + empackage(z, 'sshuttle.helpers') + empackage(z, 'sshuttle.ssnet') + empackage(z, 'sshuttle.hostwatch') + empackage(z, 'sshuttle.server') + b"\n") pyscript = r""" import sys, os; verbosity=%d; sys.stdin = os.fdopen(0, "rb"); exec(compile(sys.stdin.read(%d), "assembler.py", "exec")) """ % (helpers.verbose or 0, len(content)) pyscript = re.sub(r'\s+', ' ', pyscript.strip()) if not rhost: # ignore the --python argument when running locally; we already know # which python version works. argv = [sys.executable, '-c', pyscript] else: if ssh_cmd: sshl = shlex.split(ssh_cmd) else: sshl = ['ssh'] if port is not None: portl = ["-p", str(port)] else: portl = [] if python: pycmd = "'%s' -c '%s'" % (python, pyscript) else: pycmd = ("P=python3; $P -V 2>%s || P=python; " "exec \"$P\" -c %s") % (os.devnull, quote(pyscript)) pycmd = ("/bin/sh -c {}".format(quote(pycmd))) if password is not None: os.environ['SSHPASS'] = str(password) argv = (["sshpass", "-e"] + sshl + portl + [rhost, '--', pycmd]) else: argv = (sshl + portl + [rhost, '--', pycmd]) # Our which() function searches for programs in get_path() # directories (which include PATH). This step isn't strictly # necessary if ssh is already in the user's PATH, but it makes the # error message friendlier if the user incorrectly passes in a # custom ssh command that we cannot find. abs_path = which(argv[0]) if abs_path is None: raise Fatal("Failed to find '%s' in path %s" % (argv[0], get_path())) argv[0] = abs_path (s1, s2) = socket.socketpair() def setup(): # runs in the child process s2.close() s1a, s1b = os.dup(s1.fileno()), os.dup(s1.fileno()) s1.close() debug2('executing: %r\n' % argv) p = ssubprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup, close_fds=True, stderr=stderr) os.close(s1a) os.close(s1b) s2.sendall(content) s2.sendall(content2) return p, s2
def restore_etc_hosts(hostmap, port): # Only restore if we added hosts to /etc/hosts previously. if len(hostmap) > 0: debug2('undoing /etc/hosts changes.') rewrite_etc_hosts({}, port)
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 len(nslist_v6) > 0 \ or listenip_v6 is not None required.udp = avail.udp required.dns = len(nslist) > 0 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()
def _check_smb(hostname): return global _smb_ok if not _smb_ok: return debug2(' > smb: %s\n' % hostname) env = { 'PATH': os.environ['PATH'], 'LC_ALL': "C", } argv = ['smbclient', '-U', '%', '-L', hostname] try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null, env=env) 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 setnoread(self): if not self.shut_read: debug2('%r: done reading\n' % self) self.shut_read = True self.maybe_close()
def connect(ssh_cmd, rhostport, python, stderr, options): portl = [] if re.sub(r'.*@', '', rhostport or '').count(':') > 1: if rhostport.count(']') or rhostport.count('['): result = rhostport.split(']') rhost = result[0].strip('[') if len(result) > 1: result[1] = result[1].strip(':') if result[1] is not '': portl = ['-p', str(int(result[1]))] # can't disambiguate IPv6 colons and a port number. pass the hostname # through. else: rhost = rhostport else: # IPv4 l = (rhostport or '').rsplit(':', 1) rhost = l[0] if len(l) > 1: portl = ['-p', str(int(l[1]))] if rhost == '-': rhost = None z = zlib.compressobj(1) content = readfile('sshuttle.assembler') optdata = ''.join("%s=%r\n" % (k, v) for (k, v) in list(options.items())) optdata = optdata.encode("UTF8") content2 = (empackage(z, 'sshuttle') + empackage(z, 'sshuttle.cmdline_options', optdata) + empackage(z, 'sshuttle.helpers') + empackage(z, 'sshuttle.ssnet') + empackage(z, 'sshuttle.hostwatch') + empackage(z, 'sshuttle.server') + b"\n") pyscript = r""" import sys, os; verbosity=%d; sys.stdin = os.fdopen(0, "rb"); exec(compile(sys.stdin.read(%d), "assembler.py", "exec")) """ % (helpers.verbose or 0, len(content)) pyscript = re.sub(r'\s+', ' ', pyscript.strip()) if not rhost: # ignore the --python argument when running locally; we already know # which python version works. argv = [sys.executable, '-c', pyscript] else: if ssh_cmd: sshl = shlex.split(ssh_cmd) else: sshl = ['ssh'] if python: pycmd = "'%s' -c '%s'" % (python, pyscript) else: pycmd = ("P=python3; $P -V 2>/dev/null || P=python; " "exec \"$P\" -c %s") % quote(pyscript) pycmd = ("exec /bin/sh -c %s" % quote(pycmd)) argv = (sshl + portl + [rhost, '--', pycmd]) (s1, s2) = socket.socketpair() def setup(): # runs in the child process s2.close() s1a, s1b = os.dup(s1.fileno()), os.dup(s1.fileno()) s1.close() debug2('executing: %r\n' % argv) p = ssubprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup, close_fds=True, stderr=stderr) os.close(s1a) os.close(s1b) s2.sendall(content) s2.sendall(content2) return p, s2
def main(listenip_v6, listenip_v4, ssh_cmd, remotename, python, latency_control, dns, nslist, method_name, seed_hosts, auto_hosts, auto_nets, subnets_include, subnets_exclude, daemon, to_nameserver, pidfile, user, sudo_pythonpath): 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, sudo_pythonpath) # Get family specific subnet lists if dns: nslist += resolvconf_nameservers() if to_nameserver is not None: to_nameserver = "%s@%s" % tuple(to_nameserver[1:]) else: # option doesn't make sense if we aren't proxying dns to_nameserver = None 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 if user is not None: if getpwnam is None: raise Fatal("Routing by user not available on this system.") try: user = getpwnam(user).pw_uid except KeyError: raise Fatal("User %s does not exist." % user) required.ipv6 = len(subnets_v6) > 0 or listenip_v6 is not None required.ipv4 = len(subnets_v4) > 0 or listenip_v4 is not None required.udp = avail.udp required.dns = len(nslist) > 0 required.user = False if user is None else True # 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) debug1("User enabled: %r\n" % required.user) # bind to required ports if listenip_v4 == "auto": listenip_v4 = ('127.0.0.1', 0) if required.ipv4 and \ not any(listenip_v4[0] == sex[1] for sex in subnets_v4): subnets_exclude.append((socket.AF_INET, listenip_v4[0], 32, 0, 0)) if required.ipv6 and \ not any(listenip_v6[0] == sex[1] for sex in subnets_v6): subnets_exclude.append((socket.AF_INET6, listenip_v6[0], 128, 0, 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) # keep track of failed bindings and used ports to avoid trying to # bind to the same socket address twice in different listeners used_ports = [] # 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 used_ports.append(port) break except socket.error as e: if e.errno == errno.EADDRINUSE: last_e = e used_ports.append(port) 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) if port in used_ports: continue 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 used_ports.append(port) break except socket.error as e: if e.errno == errno.EADDRINUSE: last_e = e used_ports.append(port) 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 subnets_v6: assert required.ipv6 if redirectport_v6 == 0: raise Fatal("IPv6 subnets defined but not listening") if nslist_v6: assert required.dns assert required.ipv6 if dnsport_v6 == 0: raise Fatal("IPv6 ns servers defined but not listening") if subnets_v4: if redirectport_v4 == 0: raise Fatal("IPv4 subnets defined but not listening") if nslist_v4: 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, user) # start the client process try: return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, python, latency_control, dns_listener, seed_hosts, auto_hosts, auto_nets, daemon, to_nameserver) finally: try: if daemon: # it's not our child anymore; can't waitpid fw.p.returncode = 0 fw.done() finally: if daemon: daemon_cleanup()
def main(method_name, syslog): stdin, stdout = setup_daemon() hostmap = {} debug1('firewall manager: Starting firewall with Python version %s\n' % platform.python_version()) if method_name == "auto": method = get_auto_method() else: method = get_method(method_name) if syslog: ssyslog.start_syslog() ssyslog.stderr_to_syslog() debug1('firewall manager: ready method name %s.\n' % method.name) stdout.write('READY %s\n' % method.name) stdout.flush() # 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 = 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 = stdin.readline(128) if not line: raise Fatal('firewall: expected route but got %r' % line) elif line.startswith("NSLIST\n"): break try: (family, width, exclude, ip, fport, lport) = \ line.strip().split(',', 5) except: raise Fatal('firewall: expected route or NSLIST but got %r' % line) subnets.append((int(family), int(width), bool(int(exclude)), ip, int(fport), int(lport))) debug2('firewall manager: Got subnets: %r\n' % subnets) nslist = [] if line != 'NSLIST\n': raise Fatal('firewall: expected NSLIST but got %r' % line) while 1: line = stdin.readline(128) if not line: raise Fatal('firewall: expected nslist but got %r' % line) elif line.startswith("PORTS "): break try: (family, ip) = line.strip().split(',', 1) except: raise Fatal('firewall: expected nslist or PORTS but got %r' % line) nslist.append((int(family), ip)) debug2('firewall manager: Got partial nslist: %r\n' % nslist) debug2('firewall manager: Got nslist: %r\n' % nslist) if not line.startswith('PORTS '): raise Fatal('firewall: expected PORTS but got %r' % line) _, _, ports = line.partition(" ") ports = ports.split(",") if len(ports) != 4: raise Fatal('firewall: expected 4 ports but got %d' % len(ports)) port_v6 = int(ports[0]) port_v4 = int(ports[1]) dnsport_v6 = int(ports[2]) dnsport_v4 = int(ports[3]) 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) debug2('firewall manager: Got ports: %d,%d,%d,%d\n' % (port_v6, port_v4, dnsport_v6, dnsport_v4)) line = stdin.readline(128) if not line: raise Fatal('firewall: expected GO but got %r' % line) elif not line.startswith("GO "): raise Fatal('firewall: expected GO but got %r' % line) _, _, args = line.partition(" ") udp, user = args.strip().split(" ", 1) udp = bool(int(udp)) if user == '-': user = None debug2('firewall manager: Got udp: %r, user: %r\n' % (udp, user)) 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] try: debug1('firewall manager: setting up.\n') if subnets_v6 or nslist_v6: debug2('firewall manager: setting up IPv6.\n') method.setup_firewall(port_v6, dnsport_v6, nslist_v6, socket.AF_INET6, subnets_v6, udp, user) if subnets_v4 or nslist_v4: debug2('firewall manager: setting up IPv4.\n') method.setup_firewall(port_v4, dnsport_v4, nslist_v4, socket.AF_INET, subnets_v4, udp, user) stdout.write('STARTED\n') sdnotify.send(sdnotify.ready(), sdnotify.status('Connected')) try: 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: line = stdin.readline(128) if line.startswith('HOST '): (name, ip) = line[5:].strip().split(',', 1) hostmap[name] = ip debug2('firewall manager: setting up /etc/hosts.\n') rewrite_etc_hosts(hostmap, port_v6 or port_v4) elif line: if not method.firewall_command(line): raise Fatal('firewall: expected command, got %r' % line) else: break finally: try: sdnotify.send(sdnotify.stop()) debug1('firewall manager: undoing changes.\n') except: pass try: if subnets_v6 or nslist_v6: debug2('firewall manager: undoing IPv6 changes.\n') method.restore_firewall(port_v6, socket.AF_INET6, udp, user) except: try: debug1("firewall manager: " "Error trying to undo IPv6 firewall.\n") for line in traceback.format_exc().splitlines(): debug1("---> %s\n" % line) except: pass try: if subnets_v4 or nslist_v4: debug2('firewall manager: undoing IPv4 changes.\n') method.restore_firewall(port_v4, socket.AF_INET, udp, user) except: try: debug1("firewall manager: " "Error trying to undo IPv4 firewall.\n") for line in traceback.format_exc().splitlines(): debug1("firewall manager: ---> %s\n" % line) except: pass try: debug2('firewall manager: undoing /etc/hosts changes.\n') restore_etc_hosts(port_v6 or port_v4) except: try: debug1("firewall manager: " "Error trying to undo /etc/hosts changes.\n") for line in traceback.format_exc().splitlines(): debug1("firewall manager: ---> %s\n" % line) except: pass
def connect(ssh_cmd, rhostport, python, stderr, options): username, password, port, host = parse_hostport(rhostport) if username: rhost = "{}@{}".format(username, host) else: rhost = host z = zlib.compressobj(1) content = readfile('sshuttle.assembler') optdata = ''.join("%s=%r\n" % (k, v) for (k, v) in list(options.items())) optdata = optdata.encode("UTF8") content2 = (empackage(z, 'sshuttle') + empackage(z, 'sshuttle.cmdline_options', optdata) + empackage(z, 'sshuttle.helpers') + empackage(z, 'sshuttle.ssnet') + empackage(z, 'sshuttle.hostwatch') + empackage(z, 'sshuttle.server') + b"\n") pyscript = r""" import sys, os; verbosity=%d; sys.stdin = os.fdopen(0, "rb"); exec(compile(sys.stdin.read(%d), "assembler.py", "exec")) """ % (helpers.verbose or 0, len(content)) pyscript = re.sub(r'\s+', ' ', pyscript.strip()) if not rhost: # ignore the --python argument when running locally; we already know # which python version works. argv = [sys.executable, '-c', pyscript] else: if ssh_cmd: sshl = shlex.split(ssh_cmd) else: sshl = ['ssh'] if python: pycmd = "'%s' -c '%s'" % (python, pyscript) else: pycmd = ("P=python3; $P -V 2>%s || P=python; " "exec \"$P\" -c %s") % (os.devnull, quote(pyscript)) pycmd = ("/bin/sh -c {}".format(quote(pycmd))) if password is not None: os.environ['SSHPASS'] = str(password) argv = (["sshpass", "-e"] + sshl + ["-p", str(port)] + [rhost, '--', pycmd]) else: argv = (sshl + ["-p", str(port)] + [rhost, '--', pycmd]) (s1, s2) = socket.socketpair() def setup(): # runs in the child process s2.close() s1a, s1b = os.dup(s1.fileno()), os.dup(s1.fileno()) s1.close() debug2('executing: %r\n' % argv) p = ssubprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup, close_fds=True, stderr=stderr) os.close(s1a) os.close(s1b) s2.sendall(content) s2.sendall(content2) return p, s2
def main(method_name, syslog): stdin, stdout = setup_daemon() hostmap = {} debug1('firewall manager: Starting firewall with Python version %s\n' % platform.python_version()) if method_name == "auto": method = get_auto_method() else: method = get_method(method_name) if syslog: ssyslog.start_syslog() ssyslog.stderr_to_syslog() debug1('firewall manager: ready method name %s.\n' % method.name) stdout.write('READY %s\n' % method.name) stdout.flush() # 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 = 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 = stdin.readline(128) if not line: raise Fatal('firewall: expected route but got %r' % line) elif line.startswith("NSLIST\n"): break try: (family, width, exclude, ip) = line.strip().split(',', 3) except: raise Fatal('firewall: expected route or NSLIST but got %r' % line) subnets.append((int(family), int(width), bool(int(exclude)), ip)) debug2('firewall manager: Got subnets: %r\n' % subnets) nslist = [] if line != 'NSLIST\n': raise Fatal('firewall: expected NSLIST but got %r' % line) while 1: line = stdin.readline(128) if not line: raise Fatal('firewall: expected nslist but got %r' % line) elif line.startswith("PORTS "): break try: (family, ip) = line.strip().split(',', 1) except: raise Fatal('firewall: expected nslist or PORTS but got %r' % line) nslist.append((int(family), ip)) debug2('firewall manager: Got partial nslist: %r\n' % nslist) debug2('firewall manager: Got nslist: %r\n' % nslist) if not line.startswith('PORTS '): raise Fatal('firewall: expected PORTS but got %r' % line) _, _, ports = line.partition(" ") ports = ports.split(",") if len(ports) != 4: raise Fatal('firewall: expected 4 ports but got %n' % len(ports)) port_v6 = int(ports[0]) port_v4 = int(ports[1]) dnsport_v6 = int(ports[2]) dnsport_v4 = int(ports[3]) 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) debug2('firewall manager: Got ports: %d,%d,%d,%d\n' % (port_v6, port_v4, dnsport_v6, dnsport_v4)) line = stdin.readline(128) if not line: raise Fatal('firewall: expected GO but got %r' % line) elif not line.startswith("GO "): raise Fatal('firewall: expected GO but got %r' % line) _, _, udp = line.partition(" ") udp = bool(int(udp)) debug2('firewall manager: Got udp: %r\n' % udp) 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] try: debug1('firewall manager: setting up.\n') if len(subnets_v6) > 0 or len(nslist_v6) > 0: debug2('firewall manager: setting up IPv6.\n') method.setup_firewall( port_v6, dnsport_v6, nslist_v6, socket.AF_INET6, subnets_v6, udp) if len(subnets_v4) > 0 or len(nslist_v4) > 0: debug2('firewall manager: setting up IPv4.\n') method.setup_firewall( port_v4, dnsport_v4, nslist_v4, socket.AF_INET, subnets_v4, udp) stdout.write('STARTED\n') try: 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: line = stdin.readline(128) if line.startswith('HOST '): (name, ip) = line[5:].strip().split(',', 1) hostmap[name] = ip debug2('firewall manager: setting up /etc/hosts.\n') rewrite_etc_hosts(hostmap, port_v6 or port_v4) elif line: if not method.firewall_command(line): raise Fatal('firewall: expected command, got %r' % line) else: break finally: try: debug1('firewall manager: undoing changes.\n') except: pass try: if len(subnets_v6) > 0 or len(nslist_v6) > 0: debug2('firewall manager: undoing IPv6 changes.\n') method.restore_firewall(port_v6, socket.AF_INET6, udp) except: try: debug1("firewall manager: " "Error trying to undo IPv6 firewall.\n") for line in traceback.format_exc().splitlines(): debug1("---> %s\n" % line) except: pass try: if len(subnets_v4) > 0 or len(nslist_v4) > 0: debug2('firewall manager: undoing IPv4 changes.\n') method.restore_firewall(port_v4, socket.AF_INET, udp) except: try: debug1("firewall manager: " "Error trying to undo IPv4 firewall.\n") for line in traceback.format_exc().splitlines(): debug1("firewall manager: ---> %s\n" % line) except: pass try: debug2('firewall manager: undoing /etc/hosts changes.\n') restore_etc_hosts(port_v6 or port_v4) except: try: debug1("firewall manager: " "Error trying to undo /etc/hosts changes.\n") for line in traceback.format_exc().splitlines(): debug1("firewall manager: ---> %s\n" % line) except: pass
def main(method_name, syslog): stdin, stdout = setup_daemon() if method_name == "auto": method = get_auto_method() else: method = get_method(method_name) if syslog: ssyslog.start_syslog() ssyslog.stderr_to_syslog() debug1('firewall manager ready method name %s.\n' % method.name) stdout.write('READY %s\n' % method.name) stdout.flush() # 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 = 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 = stdin.readline(128) if not line: raise Fatal('firewall: expected route but got %r' % line) elif line.startswith("NSLIST\n"): break try: (family, width, exclude, ip) = line.strip().split(',', 3) except: raise Fatal('firewall: expected route or NSLIST but got %r' % line) subnets.append((int(family), int(width), bool(int(exclude)), ip)) debug2('Got subnets: %r\n' % subnets) nslist = [] if line != 'NSLIST\n': raise Fatal('firewall: expected NSLIST but got %r' % line) while 1: line = stdin.readline(128) if not line: raise Fatal('firewall: expected nslist but got %r' % line) elif line.startswith("PORTS "): break try: (family, ip) = line.strip().split(',', 1) except: raise Fatal('firewall: expected nslist or PORTS but got %r' % line) nslist.append((int(family), ip)) debug2('Got partial nslist: %r\n' % nslist) debug2('Got nslist: %r\n' % nslist) if not line.startswith('PORTS '): raise Fatal('firewall: expected PORTS but got %r' % line) _, _, ports = line.partition(" ") ports = ports.split(",") if len(ports) != 4: raise Fatal('firewall: expected 4 ports but got %n' % len(ports)) port_v6 = int(ports[0]) port_v4 = int(ports[1]) dnsport_v6 = int(ports[2]) dnsport_v4 = int(ports[3]) 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) debug2('Got ports: %d,%d,%d,%d\n' % (port_v6, port_v4, dnsport_v6, dnsport_v4)) line = stdin.readline(128) if not line: raise Fatal('firewall: expected GO but got %r' % line) elif not line.startswith("GO "): raise Fatal('firewall: expected GO but got %r' % line) _, _, udp = line.partition(" ") udp = bool(int(udp)) debug2('Got udp: %r\n' % udp) try: do_wait = None debug1('firewall manager: starting transproxy.\n') nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6] subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6] if port_v6 > 0: do_wait = method.setup_firewall(port_v6, dnsport_v6, nslist_v6, socket.AF_INET6, subnets_v6, udp) elif len(subnets_v6) > 0: debug1("IPv6 subnets defined but IPv6 disabled\n") nslist_v4 = [i for i in nslist if i[0] == socket.AF_INET] subnets_v4 = [i for i in subnets if i[0] == socket.AF_INET] if port_v4 > 0: do_wait = method.setup_firewall(port_v4, dnsport_v4, nslist_v4, socket.AF_INET, subnets_v4, udp) elif len(subnets_v4) > 0: debug1('IPv4 subnets defined but IPv4 disabled\n') stdout.write('STARTED\n') try: 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 is not None: do_wait() line = 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: if not method.firewall_command(line): raise Fatal('expected EOF, got %r' % line) else: break finally: try: debug1('firewall manager: undoing changes.\n') except: pass if port_v6: method.setup_firewall(port_v6, 0, [], socket.AF_INET6, [], udp) if port_v4: method.setup_firewall(port_v4, 0, [], socket.AF_INET, [], udp) restore_etc_hosts(port_v6 or port_v4)
def is_supported(self): if which("nft"): return True debug2("nft method not supported because 'nft' command is missing.") return False
def maybe_close(self): if self.shut_read and self.shut_write: debug2('%r: closing connection\n' % self) # remove the mux's reference to us. The python garbage collector # will then be able to reap our object. self.mux.channels[self.channel] = None
def main(method_name, syslog, ttl): helpers.logprefix = 'fw: ' stdin, stdout = setup_daemon() hostmap = {} debug1('Starting firewall with Python version %s' % platform.python_version()) if method_name == "auto": method = get_auto_method() else: method = get_method(method_name) if syslog: ssyslog.start_syslog() ssyslog.stderr_to_syslog() if not method.is_supported(): raise Fatal("The %s method is not supported on this machine. " "Check that the appropriate programs are in your " "PATH." % method_name) debug1('ready method name %s.' % method.name) stdout.write('READY %s\n' % method.name) stdout.flush() # 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 = stdin.readline(128) if not line: return # parent died; nothing to do subnets = [] if line != 'ROUTES\n': raise Fatal('expected ROUTES but got %r' % line) while 1: line = stdin.readline(128) if not line: raise Fatal('expected route but got %r' % line) elif line.startswith("NSLIST\n"): break try: (family, width, exclude, ip, fport, lport) = \ line.strip().split(',', 5) except BaseException: raise Fatal('expected route or NSLIST but got %r' % line) subnets.append(( int(family), int(width), bool(int(exclude)), ip, int(fport), int(lport))) debug2('Got subnets: %r' % subnets) nslist = [] if line != 'NSLIST\n': raise Fatal('expected NSLIST but got %r' % line) while 1: line = stdin.readline(128) if not line: raise Fatal('expected nslist but got %r' % line) elif line.startswith("PORTS "): break try: (family, ip) = line.strip().split(',', 1) except BaseException: raise Fatal('expected nslist or PORTS but got %r' % line) nslist.append((int(family), ip)) debug2('Got partial nslist: %r' % nslist) debug2('Got nslist: %r' % nslist) if not line.startswith('PORTS '): raise Fatal('expected PORTS but got %r' % line) _, _, ports = line.partition(" ") ports = ports.split(",") if len(ports) != 4: raise Fatal('expected 4 ports but got %d' % len(ports)) port_v6 = int(ports[0]) port_v4 = int(ports[1]) dnsport_v6 = int(ports[2]) dnsport_v4 = int(ports[3]) 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) debug2('Got ports: %d,%d,%d,%d' % (port_v6, port_v4, dnsport_v6, dnsport_v4)) line = stdin.readline(128) if not line: raise Fatal('expected GO but got %r' % line) elif not line.startswith("GO "): raise Fatal('expected GO but got %r' % line) _, _, args = line.partition(" ") udp, user, ttl, tmark = args.strip().split(" ", 3) udp = bool(int(udp)) if user == '-': user = None ttl = int(ttl) debug2('Got udp: %r, user: %r, ttl: %s, tmark: %s' % (udp, user, ttl, tmark)) 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] try: debug1('setting up.') if subnets_v6 or nslist_v6: debug2('setting up IPv6.') method.setup_firewall( port_v6, dnsport_v6, nslist_v6, socket.AF_INET6, subnets_v6, udp, user, ttl, tmark) if subnets_v4 or nslist_v4: debug2('setting up IPv4.') method.setup_firewall( port_v4, dnsport_v4, nslist_v4, socket.AF_INET, subnets_v4, udp, user, ttl, tmark) flush_systemd_dns_cache() stdout.write('STARTED\n') try: 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: line = stdin.readline(128) if line.startswith('HOST '): (name, ip) = line[5:].strip().split(',', 1) hostmap[name] = ip debug2('setting up /etc/hosts.') rewrite_etc_hosts(hostmap, port_v6 or port_v4) elif line: if not method.firewall_command(line): raise Fatal('expected command, got %r' % line) else: break finally: try: debug1('undoing changes.') except BaseException: debug2('An error occurred, ignoring it.') try: if subnets_v6 or nslist_v6: debug2('undoing IPv6 changes.') method.restore_firewall(port_v6, socket.AF_INET6, udp, user) except BaseException: try: debug1("Error trying to undo IPv6 firewall.") debug1(traceback.format_exc()) except BaseException: debug2('An error occurred, ignoring it.') try: if subnets_v4 or nslist_v4: debug2('undoing IPv4 changes.') method.restore_firewall(port_v4, socket.AF_INET, udp, user) except BaseException: try: debug1("Error trying to undo IPv4 firewall.") debug1(traceback.format_exc()) except BaseException: debug2('An error occurred, ignoring it.') try: # debug2() message printed in restore_etc_hosts() function. restore_etc_hosts(hostmap, port_v6 or port_v4) except BaseException: try: debug1("Error trying to undo /etc/hosts changes.") debug1(traceback.format_exc()) except BaseException: debug2('An error occurred, ignoring it.') try: flush_systemd_dns_cache() except BaseException: try: debug1("Error trying to flush systemd dns cache.") debug1(traceback.format_exc()) except BaseException: debug2("An error occurred, ignoring it.")
def main(listenip_v6, listenip_v4, ssh_cmd, remotename, python, latency_control, dns, nslist, method_name, seed_hosts, auto_nets, subnets_include, subnets_exclude, syslog, daemon, pidfile): if syslog: ssyslog.start_syslog() 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) features = fw.method.get_supported_features() if listenip_v6 == "auto": if features.ipv6: listenip_v6 = ('::1', 0) else: listenip_v6 = None if listenip_v4 == "auto": listenip_v4 = ('127.0.0.1', 0) udp = features.udp debug1("UDP enabled: %r\n" % udp) 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() tcp_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if udp: udp_listener = MultiListener(socket.SOCK_DGRAM) udp_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 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 dns or nslist: if dns: nslist += resolvconf_nameservers() dns = True # 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 fw.method.check_settings(udp, dns) 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) fw.setup(subnets_include, subnets_exclude, nslist, redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp) try: return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, python, latency_control, dns_listener, seed_hosts, auto_nets, syslog, 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()
def noread(self): if not self.shut_read: debug2('%r: done reading\n' % self) self.shut_read = True
def is_supported(self): if which("pfctl"): return True debug2("pf method not supported because 'pfctl' command is missing.") return False
def setnowrite(self): if not self.shut_write: debug2('%r: done writing\n' % self) self.shut_write = True self.maybe_close()
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()
def is_supported(self): if which("ipfw"): return True debug2("ipfw method not supported because 'ipfw' command is " "missing.") return False
def noread(self): if not self.shut_read: debug2('%r: done reading\n' % self) self.shut_read = True self.mux.send(self.channel, CMD_TCP_STOP_SENDING, b'') self.maybe_close()
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)
def nowrite(self): if not self.shut_write: debug2('%r: done writing\n' % self) self.shut_write = True self.mux.send(self.channel, CMD_TCP_EOF, b'') self.maybe_close()
def connect(ssh_cmd, rhostport, python, stderr, options): portl = [] if (rhostport or '').count(':') > 1: if rhostport.count(']') or rhostport.count('['): result = rhostport.split(']') rhost = result[0].strip('[') if len(result) > 1: result[1] = result[1].strip(':') if result[1] is not '': portl = ['-p', str(int(result[1]))] # can't disambiguate IPv6 colons and a port number. pass the hostname # through. else: rhost = rhostport else: # IPv4 l = (rhostport or '').split(':', 1) rhost = l[0] if len(l) > 1: portl = ['-p', str(int(l[1]))] if rhost == '-': rhost = None z = zlib.compressobj(1) content = readfile('sshuttle.assembler') optdata = ''.join("%s=%r\n" % (k, v) for (k, v) in list(options.items())) optdata = optdata.encode("UTF8") content2 = (empackage(z, 'sshuttle') + empackage(z, 'sshuttle.cmdline_options', optdata) + empackage(z, 'sshuttle.helpers') + empackage(z, 'sshuttle.ssnet') + empackage(z, 'sshuttle.hostwatch') + empackage(z, 'sshuttle.server') + b"\n") pyscript = r""" import sys, os; verbosity=%d; sys.stdin = os.fdopen(0, "rb"); exec(compile(sys.stdin.read(%d), "assembler.py", "exec")) """ % (helpers.verbose or 0, len(content)) pyscript = re.sub(r'\s+', ' ', pyscript.strip()) if not rhost: # ignore the --python argument when running locally; we already know # which python version works. argv = [sys.executable, '-c', pyscript] else: if ssh_cmd: sshl = shlex.split(ssh_cmd) else: sshl = ['ssh'] if python: pycmd = "'%s' -c '%s'" % (python, pyscript) else: pycmd = ("P=python3.5; $P -V 2>/dev/null || P=python; " "exec \"$P\" -c %s") % quote(pyscript) pycmd = ("exec /bin/sh -c %s" % quote(pycmd)) argv = (sshl + portl + [rhost, '--', pycmd]) (s1, s2) = socket.socketpair() def setup(): # runs in the child process s2.close() s1a, s1b = os.dup(s1.fileno()), os.dup(s1.fileno()) s1.close() debug2('executing: %r\n' % argv) p = ssubprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup, close_fds=True, stderr=stderr) os.close(s1a) os.close(s1b) s2.sendall(content) s2.sendall(content2) return p, s2
def maybe_close(self): if self.shut_read and self.shut_write: debug2('%r: closing connection\n' % self) # remove the mux's reference to us. del self.mux.channels[self.channel]
def connect_dst(family, ip, port): debug2('Connecting to %s:%d\n' % (ip, port)) outsock = socket.socket(family) return SockWrapper(outsock, outsock, connect_to=(ip, port), peername='%s:%d' % (ip, port))