|
@@ -57,6 +57,9 @@ import time
|
|
|
import select
|
|
|
import random
|
|
|
from optparse import OptionParser, OptionValueError
|
|
|
+import io
|
|
|
+import pwd
|
|
|
+import posix
|
|
|
|
|
|
import isc.cc
|
|
|
|
|
@@ -108,21 +111,38 @@ to avoid being restarted at exactly 10 seconds."""
|
|
|
when = time.time()
|
|
|
return max(when, self.restart_time)
|
|
|
|
|
|
+class ProcessInfoError(Exception): pass
|
|
|
+
|
|
|
class ProcessInfo:
|
|
|
"""Information about a process"""
|
|
|
|
|
|
dev_null = open(os.devnull, "w")
|
|
|
|
|
|
def __init__(self, name, args, env={}, dev_null_stdout=False,
|
|
|
- dev_null_stderr=False):
|
|
|
+ dev_null_stderr=False, uid=None, username=None):
|
|
|
self.name = name
|
|
|
self.args = args
|
|
|
self.env = env
|
|
|
self.dev_null_stdout = dev_null_stdout
|
|
|
self.dev_null_stderr = dev_null_stderr
|
|
|
self.restart_schedule = RestartSchedule()
|
|
|
+ self.uid = uid
|
|
|
+ self.username = username
|
|
|
self._spawn()
|
|
|
|
|
|
+ def _setuid(self):
|
|
|
+ """Function used before running a program that needs to run as a
|
|
|
+ different user."""
|
|
|
+ if self.uid is not None:
|
|
|
+ try:
|
|
|
+ posix.setuid(self.uid)
|
|
|
+ except OSError as e:
|
|
|
+ if e.errno == errno.EPERM:
|
|
|
+ # if we failed to change user due to permission report that
|
|
|
+ raise ProcessInfoError("Unable to change to user %s (uid %d)" % (self.username, self.uid))
|
|
|
+ else:
|
|
|
+ # otherwise simply re-raise whatever error we found
|
|
|
+ raise
|
|
|
|
|
|
def _spawn(self):
|
|
|
if self.dev_null_stdout:
|
|
@@ -138,14 +158,15 @@ class ProcessInfo:
|
|
|
# on construction (self.env).
|
|
|
spawn_env = os.environ
|
|
|
spawn_env.update(self.env)
|
|
|
- if not 'B10_FROM_SOURCE' in os.environ:
|
|
|
+ if 'B10_FROM_SOURCE' not in os.environ:
|
|
|
spawn_env['PATH'] = "@@LIBEXECDIR@@:" + spawn_env['PATH']
|
|
|
self.process = subprocess.Popen(self.args,
|
|
|
stdin=subprocess.PIPE,
|
|
|
stdout=spawn_stdout,
|
|
|
stderr=spawn_stderr,
|
|
|
close_fds=True,
|
|
|
- env=spawn_env,)
|
|
|
+ env=spawn_env,
|
|
|
+ preexec_fn=self._setuid)
|
|
|
self.pid = self.process.pid
|
|
|
self.restart_schedule.set_run_start_time()
|
|
|
|
|
@@ -155,7 +176,8 @@ class ProcessInfo:
|
|
|
class BoB:
|
|
|
"""Boss of BIND class."""
|
|
|
|
|
|
- def __init__(self, msgq_socket_file=None, auth_port=5300, verbose=False):
|
|
|
+ def __init__(self, msgq_socket_file=None, auth_port=5300, verbose=False,
|
|
|
+ setuid=None, username=None):
|
|
|
"""Initialize the Boss of BIND. This is a singleton (only one
|
|
|
can run).
|
|
|
|
|
@@ -171,6 +193,8 @@ class BoB:
|
|
|
self.processes = {}
|
|
|
self.dead_processes = {}
|
|
|
self.runnable = False
|
|
|
+ self.uid = setuid
|
|
|
+ self.username = username
|
|
|
|
|
|
def config_handler(self, new_config):
|
|
|
if self.verbose:
|
|
@@ -225,12 +249,14 @@ class BoB:
|
|
|
sys.stdout.write("[bind10] Starting b10-msgq\n")
|
|
|
try:
|
|
|
c_channel = ProcessInfo("b10-msgq", ["b10-msgq"], c_channel_env,
|
|
|
- True, not self.verbose)
|
|
|
+ True, not self.verbose, uid=self.uid,
|
|
|
+ username=self.username)
|
|
|
except Exception as e:
|
|
|
return "Unable to start b10-msgq; " + str(e)
|
|
|
self.processes[c_channel.pid] = c_channel
|
|
|
if self.verbose:
|
|
|
- sys.stdout.write("[bind10] Started b10-msgq (PID %d)\n" % c_channel.pid)
|
|
|
+ sys.stdout.write("[bind10] Started b10-msgq (PID %d)\n" %
|
|
|
+ c_channel.pid)
|
|
|
|
|
|
# now connect to the c-channel
|
|
|
cc_connect_start = time.time()
|
|
@@ -250,7 +276,8 @@ class BoB:
|
|
|
sys.stdout.write("[bind10] Starting b10-cfgmgr\n")
|
|
|
try:
|
|
|
bind_cfgd = ProcessInfo("b10-cfgmgr", ["b10-cfgmgr"],
|
|
|
- c_channel_env)
|
|
|
+ c_channel_env, uid=self.uid,
|
|
|
+ username=self.username)
|
|
|
except Exception as e:
|
|
|
c_channel.process.kill()
|
|
|
return "Unable to start b10-cfgmgr; " + str(e)
|
|
@@ -272,23 +299,6 @@ class BoB:
|
|
|
if self.verbose:
|
|
|
sys.stdout.write("[bind10] ccsession started\n")
|
|
|
|
|
|
- # start the xfrout before auth-server, to make sure every xfr-query can
|
|
|
- # be processed properly.
|
|
|
- xfrout_args = ['b10-xfrout']
|
|
|
- if self.verbose:
|
|
|
- sys.stdout.write("[bind10] Starting b10-xfrout\n")
|
|
|
- xfrout_args += ['-v']
|
|
|
- try:
|
|
|
- xfrout = ProcessInfo("b10-xfrout", xfrout_args,
|
|
|
- c_channel_env )
|
|
|
- except Exception as e:
|
|
|
- c_channel.process.kill()
|
|
|
- bind_cfgd.process.kill()
|
|
|
- return "Unable to start b10-xfrout; " + str(e)
|
|
|
- self.processes[xfrout.pid] = xfrout
|
|
|
- if self.verbose:
|
|
|
- sys.stdout.write("[bind10] Started b10-xfrout (PID %d)\n" % xfrout.pid)
|
|
|
-
|
|
|
# start b10-auth
|
|
|
# XXX: this must be read from the configuration manager in the future
|
|
|
authargs = ['b10-auth', '-p', str(self.auth_port)]
|
|
@@ -308,6 +318,28 @@ class BoB:
|
|
|
if self.verbose:
|
|
|
sys.stdout.write("[bind10] Started b10-auth (PID %d)\n" % auth.pid)
|
|
|
|
|
|
+ # everything after the authoritative server can run as non-root
|
|
|
+ if self.uid is not None:
|
|
|
+ posix.setuid(self.uid)
|
|
|
+
|
|
|
+ # start the xfrout before auth-server, to make sure every xfr-query can
|
|
|
+ # be processed properly.
|
|
|
+ xfrout_args = ['b10-xfrout']
|
|
|
+ if self.verbose:
|
|
|
+ sys.stdout.write("[bind10] Starting b10-xfrout\n")
|
|
|
+ xfrout_args += ['-v']
|
|
|
+ try:
|
|
|
+ xfrout = ProcessInfo("b10-xfrout", xfrout_args,
|
|
|
+ c_channel_env )
|
|
|
+ except Exception as e:
|
|
|
+ c_channel.process.kill()
|
|
|
+ bind_cfgd.process.kill()
|
|
|
+ return "Unable to start b10-xfrout; " + str(e)
|
|
|
+ self.processes[xfrout.pid] = xfrout
|
|
|
+ if self.verbose:
|
|
|
+ sys.stdout.write("[bind10] Started b10-xfrout (PID %d)\n" %
|
|
|
+ xfrout.pid)
|
|
|
+
|
|
|
# start b10-xfrin
|
|
|
xfrin_args = ['b10-xfrin']
|
|
|
if self.verbose:
|
|
@@ -324,7 +356,8 @@ class BoB:
|
|
|
return "Unable to start b10-xfrin; " + str(e)
|
|
|
self.processes[xfrind.pid] = xfrind
|
|
|
if self.verbose:
|
|
|
- sys.stdout.write("[bind10] Started b10-xfrin (PID %d)\n" % xfrind.pid)
|
|
|
+ sys.stdout.write("[bind10] Started b10-xfrin (PID %d)\n" %
|
|
|
+ xfrind.pid)
|
|
|
|
|
|
# start the b10-cmdctl
|
|
|
# XXX: we hardcode port 8080
|
|
@@ -344,7 +377,8 @@ class BoB:
|
|
|
return "Unable to start b10-cmdctl; " + str(e)
|
|
|
self.processes[cmd_ctrld.pid] = cmd_ctrld
|
|
|
if self.verbose:
|
|
|
- sys.stdout.write("[bind10] Started b10-cmdctl (PID %d)\n" % cmd_ctrld.pid)
|
|
|
+ sys.stdout.write("[bind10] Started b10-cmdctl (PID %d)\n" %
|
|
|
+ cmd_ctrld.pid)
|
|
|
|
|
|
self.runnable = True
|
|
|
|
|
@@ -435,11 +469,16 @@ class BoB:
|
|
|
sys.stdout.write("[bind10] Unknown child pid %d exited.\n" % pid)
|
|
|
|
|
|
def restart_processes(self):
|
|
|
- """Restart any dead processes."""
|
|
|
+ """Restart any dead processes.
|
|
|
+ Returns the time when the next process is ready to be restarted.
|
|
|
+ If the server is shutting down, returns 0.
|
|
|
+ If there are no processes, returns None.
|
|
|
+ The values returned can be safely passed into select() as the
|
|
|
+ timeout value."""
|
|
|
next_restart = None
|
|
|
# if we're shutting down, then don't restart
|
|
|
if not self.runnable:
|
|
|
- return next_restart
|
|
|
+ return 0
|
|
|
# otherwise look through each dead process and try to restart
|
|
|
still_dead = {}
|
|
|
now = time.time()
|
|
@@ -510,6 +549,10 @@ def check_port(option, opt_str, value, parser):
|
|
|
def main():
|
|
|
global options
|
|
|
global boss_of_bind
|
|
|
+ # Enforce line buffering on stdout, even when not a TTY
|
|
|
+ sys.stdout = io.TextIOWrapper(sys.stdout.detach(), line_buffering=True)
|
|
|
+
|
|
|
+
|
|
|
# Parse any command-line options.
|
|
|
parser = OptionParser(version=__version__)
|
|
|
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
|
|
@@ -520,7 +563,42 @@ def main():
|
|
|
parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
|
|
|
type="string", default=None,
|
|
|
help="UNIX domain socket file the b10-msgq daemon will use")
|
|
|
+ parser.add_option("-u", "--user", dest="user",
|
|
|
+ type="string", default=None,
|
|
|
+ help="Change user after startup (must run as root)")
|
|
|
(options, args) = parser.parse_args()
|
|
|
+ if args:
|
|
|
+ parser.print_help()
|
|
|
+ sys.exit(1)
|
|
|
+
|
|
|
+ # Check user ID.
|
|
|
+ setuid = None
|
|
|
+ username = None
|
|
|
+ if options.user:
|
|
|
+ # Try getting information about the user, assuming UID passed.
|
|
|
+ try:
|
|
|
+ pw_ent = pwd.getpwuid(int(options.user))
|
|
|
+ setuid = pw_ent.pw_uid
|
|
|
+ username = pw_ent.pw_name
|
|
|
+ except ValueError:
|
|
|
+ pass
|
|
|
+ except KeyError:
|
|
|
+ pass
|
|
|
+
|
|
|
+ # Next try getting information about the user, assuming user name
|
|
|
+ # passed.
|
|
|
+ # If the information is both a valid user name and user number, we
|
|
|
+ # prefer the name because we try it second. A minor point, hopefully.
|
|
|
+ try:
|
|
|
+ pw_ent = pwd.getpwnam(options.user)
|
|
|
+ setuid = pw_ent.pw_uid
|
|
|
+ username = pw_ent.pw_name
|
|
|
+ except KeyError:
|
|
|
+ pass
|
|
|
+
|
|
|
+ if setuid is None:
|
|
|
+ sys.stderr.write("bind10: invalid user: '%s'\n" % options.user)
|
|
|
+ sys.exit(1)
|
|
|
|
|
|
# Announce startup.
|
|
|
if options.verbose:
|
|
@@ -543,11 +621,12 @@ def main():
|
|
|
|
|
|
# Go bob!
|
|
|
boss_of_bind = BoB(options.msgq_socket_file, int(options.auth_port),
|
|
|
- options.verbose)
|
|
|
+ options.verbose, setuid, username)
|
|
|
startup_result = boss_of_bind.startup()
|
|
|
if startup_result:
|
|
|
sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result)
|
|
|
sys.exit(1)
|
|
|
+ sys.stdout.write("[bind10] BIND 10 started\n")
|
|
|
|
|
|
# In our main loop, we check for dead processes or messages
|
|
|
# on the c-channel.
|
|
@@ -584,6 +663,7 @@ def main():
|
|
|
# shutdown
|
|
|
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
|
|
|
boss_of_bind.shutdown()
|
|
|
+ sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
main()
|