summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAvery Pennarun <apenwarr@gmail.com>2010-05-02 20:54:10 -0400
committerAvery Pennarun <apenwarr@gmail.com>2010-05-02 20:54:10 -0400
commit2c2bea80bc3164e080cfac9d4c4ca21b7a592c35 (patch)
tree257dee069084810b01496f17ffa60e35c059d31c
parent7d674e9e37e60391a9355a6217d5c3da68ff41b5 (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.py50
-rw-r--r--iptables.py19
2 files changed, 54 insertions, 15 deletions
diff --git a/client.py b/client.py
index 419e339..6d0f88b 100644
--- a/client.py
+++ b/client.py
@@ -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, [])