mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-02-16 18:32:03 +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
111
client.py
111
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 compat.ssubprocess as ssubprocess
|
||||||
import helpers, ssnet, ssh
|
import helpers, ssnet, ssh, ssyslog
|
||||||
from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
@ -21,6 +21,79 @@ def _islocal(ip):
|
|||||||
return True # it's a local IP, or there would have been an error
|
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):
|
def original_dst(sock):
|
||||||
try:
|
try:
|
||||||
SO_ORIGINAL_DST = 80
|
SO_ORIGINAL_DST = 80
|
||||||
@ -46,6 +119,8 @@ class FirewallClient:
|
|||||||
argvbase = ([sys.argv[0]] +
|
argvbase = ([sys.argv[0]] +
|
||||||
['-v'] * (helpers.verbose or 0) +
|
['-v'] * (helpers.verbose or 0) +
|
||||||
['--firewall', str(port)])
|
['--firewall', str(port)])
|
||||||
|
if ssyslog._p:
|
||||||
|
argvbase += ['--syslog']
|
||||||
argv_tries = [
|
argv_tries = [
|
||||||
['sudo', '-p', '[local sudo] Password: '] + argvbase,
|
['sudo', '-p', '[local sudo] Password: '] + argvbase,
|
||||||
['su', '-c', ' '.join(argvbase)],
|
['su', '-c', ' '.join(argvbase)],
|
||||||
@ -114,15 +189,18 @@ class FirewallClient:
|
|||||||
raise Fatal('cleanup: %r returned %d' % (self.argv, rv))
|
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 = []
|
handlers = []
|
||||||
if helpers.verbose >= 1:
|
if helpers.verbose >= 1:
|
||||||
helpers.logprefix = 'c : '
|
helpers.logprefix = 'c : '
|
||||||
else:
|
else:
|
||||||
helpers.logprefix = 'client: '
|
helpers.logprefix = 'client: '
|
||||||
debug1('connecting to server...\n')
|
debug1('connecting to server...\n')
|
||||||
|
|
||||||
try:
|
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:
|
except socket.error, e:
|
||||||
if e.args[0] == errno.EPIPE:
|
if e.args[0] == errno.EPIPE:
|
||||||
raise Fatal("failed to establish ssh session (1)")
|
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'
|
raise Fatal('expected server init string %r; got %r'
|
||||||
% (expected, initstring))
|
% (expected, initstring))
|
||||||
debug1('connected.\n')
|
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):
|
def onroutes(routestr):
|
||||||
if auto_nets:
|
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,
|
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')
|
debug1('Starting sshuttle proxy.\n')
|
||||||
listener = socket.socket()
|
listener = socket.socket()
|
||||||
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
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:
|
try:
|
||||||
return _main(listener, fw, ssh_cmd, remotename,
|
return _main(listener, fw, ssh_cmd, remotename,
|
||||||
python, seed_hosts, auto_nets)
|
python, seed_hosts, auto_nets, syslog, daemon)
|
||||||
finally:
|
finally:
|
||||||
|
try:
|
||||||
|
if daemon:
|
||||||
|
# it's not our child anymore; can't waitpid
|
||||||
|
fw.p.returncode = 0
|
||||||
fw.done()
|
fw.done()
|
||||||
|
finally:
|
||||||
|
if daemon:
|
||||||
|
daemon_cleanup()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import re, errno
|
import re, errno
|
||||||
import compat.ssubprocess as ssubprocess
|
import compat.ssubprocess as ssubprocess
|
||||||
import helpers
|
import helpers, ssyslog
|
||||||
from helpers import *
|
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
|
# 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
|
# supercede it in the transproxy list, at least, so the leftover rules
|
||||||
# are hopefully harmless.
|
# are hopefully harmless.
|
||||||
def main(port):
|
def main(port, syslog):
|
||||||
assert(port > 0)
|
assert(port > 0)
|
||||||
assert(port <= 65535)
|
assert(port <= 65535)
|
||||||
|
|
||||||
@ -235,6 +235,10 @@ def main(port):
|
|||||||
# can read from it.
|
# can read from it.
|
||||||
os.dup2(1, 0)
|
os.dup2(1, 0)
|
||||||
|
|
||||||
|
if syslog:
|
||||||
|
ssyslog.start_syslog()
|
||||||
|
ssyslog.stderr_to_syslog()
|
||||||
|
|
||||||
debug1('firewall manager ready.\n')
|
debug1('firewall manager ready.\n')
|
||||||
sys.stdout.write('READY\n')
|
sys.stdout.write('READY\n')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
14
main.py
14
main.py
@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import sys, os, re
|
import sys, os, re
|
||||||
import helpers, options, client, server, firewall, hostwatch
|
import helpers, options, client, server, firewall, hostwatch
|
||||||
|
import compat.ssubprocess as ssubprocess
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
|
|
||||||
@ -46,8 +47,9 @@ def parse_ipport(s):
|
|||||||
|
|
||||||
optspec = """
|
optspec = """
|
||||||
sshuttle [-l [ip:]port] [-r [username@]sshserver[:port]] <subnets...>
|
sshuttle [-l [ip:]port] [-r [username@]sshserver[:port]] <subnets...>
|
||||||
sshuttle --firewall <port> <subnets...>
|
|
||||||
sshuttle --server
|
sshuttle --server
|
||||||
|
sshuttle --firewall <port> <subnets...>
|
||||||
|
sshuttle --hostwatch
|
||||||
--
|
--
|
||||||
l,listen= transproxy to this ip address and port number [127.0.0.1:0]
|
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
|
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
|
v,verbose increase debug message verbosity
|
||||||
e,ssh-cmd= the command to use to connect to the remote [ssh]
|
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)
|
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)
|
server (internal use only)
|
||||||
firewall (internal use only)
|
firewall (internal use only)
|
||||||
hostwatch (internal use only)
|
hostwatch (internal use only)
|
||||||
@ -65,6 +70,8 @@ hostwatch (internal use only)
|
|||||||
o = options.Options('sshuttle', optspec)
|
o = options.Options('sshuttle', optspec)
|
||||||
(opt, flags, extra) = o.parse(sys.argv[1:])
|
(opt, flags, extra) = o.parse(sys.argv[1:])
|
||||||
|
|
||||||
|
if opt.daemon:
|
||||||
|
opt.syslog = 1
|
||||||
helpers.verbose = opt.verbose
|
helpers.verbose = opt.verbose
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -75,7 +82,7 @@ try:
|
|||||||
elif opt.firewall:
|
elif opt.firewall:
|
||||||
if len(extra) != 1:
|
if len(extra) != 1:
|
||||||
o.fatal('exactly one argument expected')
|
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:
|
elif opt.hostwatch:
|
||||||
sys.exit(hostwatch.hw_main(extra))
|
sys.exit(hostwatch.hw_main(extra))
|
||||||
else:
|
else:
|
||||||
@ -104,7 +111,8 @@ try:
|
|||||||
sh,
|
sh,
|
||||||
opt.auto_nets,
|
opt.auto_nets,
|
||||||
parse_subnets(includes),
|
parse_subnets(includes),
|
||||||
parse_subnets(excludes)))
|
parse_subnets(excludes),
|
||||||
|
opt.syslog, opt.daemon, opt.pidfile))
|
||||||
except Fatal, e:
|
except Fatal, e:
|
||||||
log('fatal: %s\n' % e)
|
log('fatal: %s\n' % e)
|
||||||
sys.exit(99)
|
sys.exit(99)
|
||||||
|
@ -166,6 +166,7 @@ def main():
|
|||||||
|
|
||||||
while mux.ok:
|
while mux.ok:
|
||||||
if hw.pid:
|
if hw.pid:
|
||||||
|
assert(hw.pid > 0)
|
||||||
(rpid, rv) = os.waitpid(hw.pid, os.WNOHANG)
|
(rpid, rv) = os.waitpid(hw.pid, os.WNOHANG)
|
||||||
if rpid:
|
if rpid:
|
||||||
raise Fatal('hostwatch exited unexpectedly: code 0x%04x\n' % rv)
|
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)
|
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]
|
main_exe = sys.argv[0]
|
||||||
portl = []
|
portl = []
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ def connect(ssh_cmd, rhostport, python):
|
|||||||
s1.close()
|
s1.close()
|
||||||
debug2('executing: %r\n' % argv)
|
debug2('executing: %r\n' % argv)
|
||||||
p = ssubprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup,
|
p = ssubprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup,
|
||||||
close_fds=True)
|
close_fds=True, stderr=stderr)
|
||||||
os.close(s1a)
|
os.close(s1a)
|
||||||
os.close(s1b)
|
os.close(s1b)
|
||||||
s2.sendall(content)
|
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