diff options
author | Avery Pennarun <apenwarr@gmail.com> | 2011-01-01 00:22:43 -0800 |
---|---|---|
committer | Avery Pennarun <apenwarr@gmail.com> | 2011-01-01 00:22:43 -0800 |
commit | e6d7c44e277080347cfa95e56b285f5aa08bb97a (patch) | |
tree | 3e5ceb654b21b643494c39c96b4423f437b7686f | |
parent | 651b607361280c92c5d8d92567fed90ce856261e (diff) | |
parent | 5bf6e40682c0957931fe09eddbe598fb48519702 (diff) |
Merge branch 'daemon'
* daemon:
daemonization: make sure the firewall subproc sends to syslog too.
Rearrange daemonization/syslog stuff and make it more resilient.
run in background (daemon) and option
-rw-r--r-- | client.py | 113 | ||||
-rw-r--r-- | firewall.py | 8 | ||||
-rwxr-xr-x | main.py | 14 | ||||
-rw-r--r-- | server.py | 1 | ||||
-rw-r--r-- | ssh.py | 4 | ||||
-rw-r--r-- | ssyslog.py | 16 |
6 files changed, 142 insertions, 14 deletions
@@ -1,6 +1,6 @@ -import struct, socket, select, errno, re +import struct, socket, select, errno, re, signal import compat.ssubprocess as ssubprocess -import helpers, ssnet, ssh +import helpers, ssnet, ssh, ssyslog from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper from helpers import * @@ -21,6 +21,79 @@ def _islocal(ip): return True # it's a local IP, or there would have been an error +def got_signal(signum, frame): + log('exiting on signal %d\n' % signum) + sys.exit(1) + + +_pidname = None +def check_daemon(pidfile): + global _pidname + _pidname = os.path.abspath(pidfile) + try: + oldpid = open(_pidname).read(1024) + except IOError, e: + if e.errno == errno.ENOENT: + return # no pidfile, ok + else: + raise Fatal("can't read %s: %s" % (_pidname, e)) + if not oldpid: + os.unlink(_pidname) + return # invalid pidfile, ok + oldpid = int(oldpid.strip() or 0) + if oldpid <= 0: + os.unlink(_pidname) + return # invalid pidfile, ok + try: + os.kill(oldpid, 0) + except OSError, e: + if e.errno == errno.ESRCH: + os.unlink(_pidname) + return # outdated pidfile, ok + elif e.errno == errno.EPERM: + pass + else: + raise + raise Fatal("%s: sshuttle is already running (pid=%d)" + % (_pidname, oldpid)) + + +def daemonize(): + if os.fork(): + os._exit(0) + os.setsid() + if os.fork(): + os._exit(0) + + outfd = os.open(_pidname, os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0666) + try: + os.write(outfd, '%d\n' % os.getpid()) + finally: + os.close(outfd) + os.chdir("/") + + # Normal exit when killed, or try/finally won't work and the pidfile won't + # be deleted. + signal.signal(signal.SIGTERM, got_signal) + + si = open('/dev/null', 'r+') + os.dup2(si.fileno(), 0) + os.dup2(si.fileno(), 1) + si.close() + + ssyslog.stderr_to_syslog() + + +def daemon_cleanup(): + try: + os.unlink(_pidname) + except OSError, e: + if e.errno == errno.ENOENT: + pass + else: + raise + + def original_dst(sock): try: SO_ORIGINAL_DST = 80 @@ -46,6 +119,8 @@ class FirewallClient: argvbase = ([sys.argv[0]] + ['-v'] * (helpers.verbose or 0) + ['--firewall', str(port)]) + if ssyslog._p: + argvbase += ['--syslog'] argv_tries = [ ['sudo', '-p', '[local sudo] Password: '] + argvbase, ['su', '-c', ' '.join(argvbase)], @@ -114,15 +189,18 @@ class FirewallClient: raise Fatal('cleanup: %r returned %d' % (self.argv, rv)) -def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets): +def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets, + syslog, daemon): handlers = [] if helpers.verbose >= 1: helpers.logprefix = 'c : ' else: helpers.logprefix = 'client: ' debug1('connecting to server...\n') + try: - (serverproc, serversock) = ssh.connect(ssh_cmd, remotename, python) + (serverproc, serversock) = ssh.connect(ssh_cmd, remotename, python, + stderr=ssyslog._p and ssyslog._p.stdin) except socket.error, e: if e.args[0] == errno.EPIPE: raise Fatal("failed to establish ssh session (1)") @@ -148,6 +226,12 @@ def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets): raise Fatal('expected server init string %r; got %r' % (expected, initstring)) debug1('connected.\n') + if daemon: + daemonize() + log('daemonizing (%s).\n' % _pidname) + elif syslog: + debug1('switching to syslog.\n') + ssyslog.stderr_to_syslog() def onroutes(routestr): if auto_nets: @@ -219,7 +303,15 @@ def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets): def main(listenip, ssh_cmd, remotename, python, seed_hosts, auto_nets, - subnets_include, subnets_exclude): + subnets_include, subnets_exclude, syslog, daemon, pidfile): + if syslog: + ssyslog.start_syslog() + if daemon: + try: + check_daemon(pidfile) + except Fatal, e: + log("%s\n" % e) + return 5 debug1('Starting sshuttle proxy.\n') listener = socket.socket() listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -250,6 +342,13 @@ def main(listenip, ssh_cmd, remotename, python, seed_hosts, auto_nets, try: return _main(listener, fw, ssh_cmd, remotename, - python, seed_hosts, auto_nets) + python, seed_hosts, auto_nets, syslog, daemon) finally: - fw.done() + try: + if daemon: + # it's not our child anymore; can't waitpid + fw.p.returncode = 0 + fw.done() + finally: + if daemon: + daemon_cleanup() diff --git a/firewall.py b/firewall.py index 36ba768..044ac52 100644 --- a/firewall.py +++ b/firewall.py @@ -1,6 +1,6 @@ import re, errno import compat.ssubprocess as ssubprocess -import helpers +import helpers, ssyslog from helpers import * @@ -216,7 +216,7 @@ def restore_etc_hosts(port): # 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(port): +def main(port, syslog): assert(port > 0) assert(port <= 65535) @@ -235,6 +235,10 @@ def main(port): # can read from it. os.dup2(1, 0) + if syslog: + ssyslog.start_syslog() + ssyslog.stderr_to_syslog() + debug1('firewall manager ready.\n') sys.stdout.write('READY\n') sys.stdout.flush() @@ -1,6 +1,7 @@ #!/usr/bin/env python import sys, os, re import helpers, options, client, server, firewall, hostwatch +import compat.ssubprocess as ssubprocess from helpers import * @@ -46,8 +47,9 @@ def parse_ipport(s): optspec = """ sshuttle [-l [ip:]port] [-r [username@]sshserver[:port]] <subnets...> -sshuttle --firewall <port> <subnets...> sshuttle --server +sshuttle --firewall <port> <subnets...> +sshuttle --hostwatch -- l,listen= transproxy to this ip address and port number [127.0.0.1:0] H,auto-hosts scan for remote hostnames and update local /etc/hosts @@ -58,6 +60,9 @@ x,exclude= exclude this subnet (can be used more than once) v,verbose increase debug message verbosity e,ssh-cmd= the command to use to connect to the remote [ssh] seed-hosts= with -H, use these hostnames for initial scan (comma-separated) +D,daemon run in the background as a daemon +syslog send log messages to syslog (default if you use --daemon) +pidfile= pidfile name (only if using --daemon) [./sshuttle.pid] server (internal use only) firewall (internal use only) hostwatch (internal use only) @@ -65,6 +70,8 @@ hostwatch (internal use only) o = options.Options('sshuttle', optspec) (opt, flags, extra) = o.parse(sys.argv[1:]) +if opt.daemon: + opt.syslog = 1 helpers.verbose = opt.verbose try: @@ -75,7 +82,7 @@ try: elif opt.firewall: if len(extra) != 1: o.fatal('exactly one argument expected') - sys.exit(firewall.main(int(extra[0]))) + sys.exit(firewall.main(int(extra[0]), opt.syslog)) elif opt.hostwatch: sys.exit(hostwatch.hw_main(extra)) else: @@ -104,7 +111,8 @@ try: sh, opt.auto_nets, parse_subnets(includes), - parse_subnets(excludes))) + parse_subnets(excludes), + opt.syslog, opt.daemon, opt.pidfile)) except Fatal, e: log('fatal: %s\n' % e) sys.exit(99) @@ -166,6 +166,7 @@ def main(): while mux.ok: if hw.pid: + assert(hw.pid > 0) (rpid, rv) = os.waitpid(hw.pid, os.WNOHANG) if rpid: raise Fatal('hostwatch exited unexpectedly: code 0x%04x\n' % rv) @@ -21,7 +21,7 @@ def empackage(z, filename): return '%s\n%d\n%s' % (basename,len(content), content) -def connect(ssh_cmd, rhostport, python): +def connect(ssh_cmd, rhostport, python, stderr): main_exe = sys.argv[0] portl = [] @@ -87,7 +87,7 @@ def connect(ssh_cmd, rhostport, python): s1.close() debug2('executing: %r\n' % argv) p = ssubprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup, - close_fds=True) + close_fds=True, stderr=stderr) os.close(s1a) os.close(s1b) s2.sendall(content) diff --git a/ssyslog.py b/ssyslog.py new file mode 100644 index 0000000..9958c9d --- /dev/null +++ b/ssyslog.py @@ -0,0 +1,16 @@ +import sys, os +from compat import ssubprocess + + +_p = None +def start_syslog(): + global _p + _p = ssubprocess.Popen(['logger', + '-p', 'daemon.info', + '-t', 'sshuttle'], stdin=ssubprocess.PIPE) + + +def stderr_to_syslog(): + sys.stdout.flush() + sys.stderr.flush() + os.dup2(_p.stdin.fileno(), 2) |