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

View File

@ -11,7 +11,7 @@ def chain_exists(name):
return True
rv = p.wait()
if rv:
raise Exception('%r returned %d' % (argv, rv))
raise Fatal('%r returned %d' % (argv, rv))
def ipt(*args):
@ -19,7 +19,7 @@ def ipt(*args):
debug1('>> %s\n' % ' '.join(argv))
rv = subprocess.call(argv)
if rv:
raise Exception('%r returned %d' % (argv, rv))
raise Fatal('%r returned %d' % (argv, rv))
def do_it(port, subnets):
@ -66,6 +66,14 @@ def main(port, subnets):
assert(port > 0)
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')
sys.stdout.write('READY\n')
sys.stdout.flush()
@ -77,14 +85,17 @@ def main(port, subnets):
# 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).
sys.stdin.readline()
sys.stdin.readline(128)
try:
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
# to stay running so that we don't need a *second* password
# authentication at shutdown time - that cleanup is important!
while sys.stdin.readline():
while sys.stdin.readline(128):
pass
finally:
do_it(port, [])