diff options
author | Avery Pennarun <apenwarr@gmail.com> | 2010-05-02 20:54:10 -0400 |
---|---|---|
committer | Avery Pennarun <apenwarr@gmail.com> | 2010-05-02 20:54:10 -0400 |
commit | 2c2bea80bc3164e080cfac9d4c4ca21b7a592c35 (patch) | |
tree | 257dee069084810b01496f17ffa60e35c059d31c | |
parent | 7d674e9e37e60391a9355a6217d5c3da68ff41b5 (diff) |
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.
-rw-r--r-- | client.py | 50 | ||||
-rw-r--r-- | iptables.py | 19 |
2 files changed, 54 insertions, 15 deletions
@@ -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)) diff --git a/iptables.py b/iptables.py index 77c4ecf..3681b1b 100644 --- a/iptables.py +++ b/iptables.py @@ -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, []) |