File: //lib/python3.6/site-packages/sshuttle/firewall.py
import errno
import socket
import signal
import sys
import os
import platform
import traceback
import sshuttle.ssyslog as ssyslog
from sshuttle.helpers import debug1, debug2, Fatal
from sshuttle.methods import get_auto_method, get_method
HOSTSFILE = '/etc/hosts'
def rewrite_etc_hosts(hostmap, port):
BAKFILE = '%s.sbak' % HOSTSFILE
APPEND = '# sshuttle-firewall-%d AUTOCREATED' % port
old_content = ''
st = None
try:
old_content = open(HOSTSFILE).read()
st = os.stat(HOSTSFILE)
except IOError as e:
if e.errno == errno.ENOENT:
pass
else:
raise
if old_content.strip() and not os.path.exists(BAKFILE):
os.link(HOSTSFILE, BAKFILE)
tmpname = "%s.%d.tmp" % (HOSTSFILE, port)
f = open(tmpname, 'w')
for line in old_content.rstrip().split('\n'):
if line.find(APPEND) >= 0:
continue
f.write('%s\n' % line)
for (name, ip) in sorted(hostmap.items()):
f.write('%-30s %s\n' % ('%s %s' % (ip, name), APPEND))
f.close()
if st is not None:
os.chown(tmpname, st.st_uid, st.st_gid)
os.chmod(tmpname, st.st_mode)
else:
os.chown(tmpname, 0, 0)
os.chmod(tmpname, 0o600)
os.rename(tmpname, HOSTSFILE)
def restore_etc_hosts(port):
rewrite_etc_hosts({}, port)
# Isolate function that needs to be replaced for tests
def setup_daemon():
if os.getuid() != 0:
raise Fatal('you must be root (or enable su/sudo) to set the firewall')
# don't disappear if our controlling terminal or stdout/stderr
# disappears; we still have to clean up.
signal.signal(signal.SIGHUP, signal.SIG_IGN)
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
signal.signal(signal.SIGTERM, signal.SIG_IGN)
signal.signal(signal.SIGINT, signal.SIG_IGN)
# ctrl-c shouldn't be passed along to me. When the main sshuttle dies,
# I'll die automatically.
os.setsid()
# because of limitations of the 'su' command, the *real* stdin/stdout
# are both attached to stdout initially. Clone stdout into stdin so we
# can read from it.
os.dup2(1, 0)
return sys.stdin, sys.stdout
# Note that we're sorting in a very particular order:
# we need to go from smaller, more specific, port ranges, to larger,
# less-specific, port ranges. At each level, we order by subnet
# width, from most-specific subnets (largest swidth) to
# least-specific. On ties, excludes come first.
# s:(inet, subnet width, exclude flag, subnet, first port, last port)
def subnet_weight(s):
return (-s[-1] + (s[-2] or -65535), s[1], s[2])
# This is some voodoo for setting up the kernel's transparent
# proxying stuff. If subnets is empty, we just delete our sshuttle rules;
# otherwise we delete it, then make them from scratch.
#
# This code is supposed to clean up after itself by deleting its rules on
# exit. In case that fails, it's not the end of the world; future runs will
# supercede it in the transproxy list, at least, so the leftover rules
# are hopefully harmless.
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 BaseException:
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 BaseException:
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')
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 BaseException:
debug2('An error occurred, ignoring it.')
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 BaseException:
try:
debug1("firewall manager: "
"Error trying to undo IPv6 firewall.\n")
for line in traceback.format_exc().splitlines():
debug1("---> %s\n" % line)
except BaseException:
debug2('An error occurred, ignoring it.')
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 BaseException:
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 BaseException:
debug2('An error occurred, ignoring it.')
try:
debug2('firewall manager: undoing /etc/hosts changes.\n')
restore_etc_hosts(port_v6 or port_v4)
except BaseException:
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 BaseException:
debug2('An error occurred, ignoring it.')