mirror of
https://github.com/sshuttle/sshuttle.git
synced 2024-11-25 09:23:48 +01:00
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
This commit is contained in:
commit
e6d7c44e27
113
client.py
113
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()
|
||||
|
@ -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()
|
||||
|
14
main.py
14
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)
|
||||
|
@ -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)
|
||||
|
4
ssh.py
4
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)
|
||||
|
16
ssyslog.py
Normal file
16
ssyslog.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user