iptables: try launching with sudo, then su, then directly.

Previous versions depended on having 'sudo' in your PATH.  Now that we can
feel safe that --iptables will clean up properly when you exit, and it
doesn't need to authenticate twice, the advantages of sudo aren't strictly
needed.  Good old 'su' is a reasonable fallback - and everybody has it,
which is nice.

Unfortunately su doesn't let you redirect stdin, so I had to play a stupid
fd trick to make it work.
This commit is contained in:
Avery Pennarun 2010-05-02 20:54:10 -04:00
parent 7d674e9e37
commit 2c2bea80bc
2 changed files with 54 additions and 15 deletions

View File

@ -18,13 +18,38 @@ class IPTables:
self.port = port self.port = port
self.subnets = subnets self.subnets = subnets
subnets_str = ['%s/%d' % (ip,width) for ip,width in subnets] subnets_str = ['%s/%d' % (ip,width) for ip,width in subnets]
self.argv = (['sudo', sys.argv[0]] + argvbase = ([sys.argv[0]] +
['-v'] * (helpers.verbose or 0) + ['-v'] * (helpers.verbose or 0) +
['--iptables', str(port)] + subnets_str) ['--iptables', str(port)] + subnets_str)
self.p = subprocess.Popen(self.argv, argv_tries = [
stdin=subprocess.PIPE, ['sudo'] + argvbase,
stdout=subprocess.PIPE) ['su', '-c', ' '.join(argvbase)],
line = self.p.stdout.readline() argvbase
]
# we can't use stdin/stdout=subprocess.PIPE here, as we normally would,
# because stupid Linux 'su' requires that stdin be attached to a tty.
# Instead, attach a *bidirectional* socket to its stdout, and use
# that for talking in both directions.
(s1,s2) = socket.socketpair()
def setup():
# run in the child process
s2.close()
e = None
for argv in argv_tries:
try:
self.p = subprocess.Popen(argv, stdout=s1, preexec_fn=setup)
e = None
break
except OSError, e:
pass
self.argv = argv
s1.close()
self.pfile = s2.makefile('wb+')
if e:
log('Spawning iptables: %r\n' % self.argv)
raise Fatal(e)
line = self.pfile.readline()
self.check() self.check()
if line != 'READY\n': if line != 'READY\n':
raise Fatal('%r expected READY, got %r' % (self.argv, line)) raise Fatal('%r expected READY, got %r' % (self.argv, line))
@ -35,12 +60,15 @@ class IPTables:
raise Fatal('%r returned %d' % (self.argv, rv)) raise Fatal('%r returned %d' % (self.argv, rv))
def start(self): def start(self):
self.p.stdin.write('GO\n') self.pfile.write('GO\n')
self.p.stdin.flush() self.pfile.flush()
line = self.pfile.readline()
self.check()
if line != 'STARTED\n':
raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
def done(self): def done(self):
self.p.stdin.close() self.pfile.close()
self.p.stdout.close()
rv = self.p.wait() rv = self.p.wait()
if rv: if rv:
raise Fatal('cleanup: %r returned %d' % (self.argv, rv)) raise Fatal('cleanup: %r returned %d' % (self.argv, rv))

View File

@ -11,7 +11,7 @@ def chain_exists(name):
return True return True
rv = p.wait() rv = p.wait()
if rv: if rv:
raise Exception('%r returned %d' % (argv, rv)) raise Fatal('%r returned %d' % (argv, rv))
def ipt(*args): def ipt(*args):
@ -19,7 +19,7 @@ def ipt(*args):
debug1('>> %s\n' % ' '.join(argv)) debug1('>> %s\n' % ' '.join(argv))
rv = subprocess.call(argv) rv = subprocess.call(argv)
if rv: if rv:
raise Exception('%r returned %d' % (argv, rv)) raise Fatal('%r returned %d' % (argv, rv))
def do_it(port, subnets): def do_it(port, subnets):
@ -66,6 +66,14 @@ def main(port, subnets):
assert(port > 0) assert(port > 0)
assert(port <= 65535) assert(port <= 65535)
if os.getuid() != 0:
raise Fatal('you must be root (or enable su/sudo) to set up iptables')
# 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)
debug1('iptables manager ready.\n') debug1('iptables manager ready.\n')
sys.stdout.write('READY\n') sys.stdout.write('READY\n')
sys.stdout.flush() sys.stdout.flush()
@ -77,14 +85,17 @@ def main(port, subnets):
# we wait until we get some input before creating the rules. That way, # 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 # sshuttle can launch us as early as possible (and get sudo password
# authentication as early in the startup process as possible). # authentication as early in the startup process as possible).
sys.stdin.readline() sys.stdin.readline(128)
try: try:
do_it(port, subnets) do_it(port, subnets)
sys.stdout.write('STARTED\n')
sys.stdout.flush()
# Now we wait until EOF or any other kind of exception. We need # 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 # to stay running so that we don't need a *second* password
# authentication at shutdown time - that cleanup is important! # authentication at shutdown time - that cleanup is important!
while sys.stdin.readline(): while sys.stdin.readline(128):
pass pass
finally: finally:
do_it(port, []) do_it(port, [])