summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAvery Pennarun <apenwarr@gmail.com>2011-01-01 00:22:43 -0800
committerAvery Pennarun <apenwarr@gmail.com>2011-01-01 00:22:43 -0800
commite6d7c44e277080347cfa95e56b285f5aa08bb97a (patch)
tree3e5ceb654b21b643494c39c96b4423f437b7686f
parent651b607361280c92c5d8d92567fed90ce856261e (diff)
parent5bf6e40682c0957931fe09eddbe598fb48519702 (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.py113
-rw-r--r--firewall.py8
-rwxr-xr-xmain.py14
-rw-r--r--server.py1
-rw-r--r--ssh.py4
-rw-r--r--ssyslog.py16
6 files changed, 142 insertions, 14 deletions
diff --git a/client.py b/client.py
index 092bc2a..daa1e6a 100644
--- a/client.py
+++ b/client.py
@@ -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()
diff --git a/main.py b/main.py
index a3ac837..66954f8 100755
--- a/main.py
+++ b/main.py
@@ -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)
diff --git a/server.py b/server.py
index 08af657..24dd462 100644
--- a/server.py
+++ b/server.py
@@ -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)
diff --git a/ssh.py b/ssh.py
index dd9e042..ac7f411 100644
--- a/ssh.py
+++ b/ssh.py
@@ -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)