import errno
import re
import signal
import time
import subprocess as ssubprocess
import os
import sys
import platform
import sshuttle.helpers as helpers
import sshuttle.ssnet as ssnet
import sshuttle.ssh as ssh
import sshuttle.ssyslog as ssyslog
import sshuttle.sdnotify as sdnotify
from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, islocal, \
resolvconf_nameservers, which
from sshuttle.methods import get_method, Features
from sshuttle import __version__
try:
from pwd import getpwnam
except ImportError:
getpwnam = None
try:
# try getting recvmsg from python
import socket as pythonsocket
getattr(pythonsocket.socket, "recvmsg")
socket = pythonsocket
except AttributeError:
# try getting recvmsg from socket_ext library
try:
import socket_ext
getattr(socket_ext.socket, "recvmsg")
socket = socket_ext
except ImportError:
import socket
_extra_fd = os.open(os.devnull, os.O_RDONLY)
def got_signal(signum, frame):
log('exiting on signal %d' % signum)
sys.exit(1)
# Filename of the pidfile created by the sshuttle client.
_pidname = None
def check_daemon(pidfile):
global _pidname
_pidname = os.path.abspath(pidfile)
try:
oldpid = open(_pidname).read(1024)
except IOError as 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 as 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():
# Try to open the pidfile prior to forking. If there is a problem,
# the client can then exit with a proper exit status code and
# message.
try:
outfd = os.open(_pidname, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o666)
except PermissionError:
# User will have to look in syslog for error message since
# --daemon implies --syslog, all output gets redirected to
# syslog.
raise Fatal("failed to create/write pidfile %s" % _pidname)
# Create a daemon process with a new session id.
if os.fork():
os._exit(0)
os.setsid()
if os.fork():
os._exit(0)
# Write pid to the pidfile.
try:
os.write(outfd, b'%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(os.devnull, 'r+')
os.dup2(si.fileno(), 0)
os.dup2(si.fileno(), 1)
si.close()
def daemon_cleanup():
try:
os.unlink(_pidname)
except OSError as e:
if e.errno == errno.ENOENT:
pass
else:
raise
class MultiListener:
def __init__(self, kind=socket.SOCK_STREAM, proto=0):
self.type = kind
self.proto = proto
self.v6 = None
self.v4 = None
self.bind_called = False
def setsockopt(self, level, optname, value):
assert(self.bind_called)
if self.v6:
self.v6.setsockopt(level, optname, value)
if self.v4:
self.v4.setsockopt(level, optname, value)
def add_handler(self, handlers, callback, method, mux):
assert(self.bind_called)
socks = []
if self.v6:
socks.append(self.v6)
if self.v4:
socks.append(self.v4)
handlers.append(
Handler(
socks,
lambda sock: callback(sock, method, mux, handlers)
)
)
def listen(self, backlog):
assert(self.bind_called)
if self.v6:
self.v6.listen(backlog)
if self.v4:
try:
self.v4.listen(backlog)
except socket.error as e:
# on some systems v4 bind will fail if the v6 suceeded,
# in this case the v6 socket will receive v4 too.
if e.errno == errno.EADDRINUSE and self.v6:
self.<