[svn] commit: r2330 - in /trunk/src/bin/bind10: bind10.py.in tests/args_test.py tests/bind10_test.in
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Jun 29 19:54:04 UTC 2010
Author: shane
Date: Tue Jun 29 19:54:04 2010
New Revision: 2330
Log:
Optionally drop priviliges in the boss process.
This involves adding a "-u" flag, then invoking setuid() if passed.
A set of tests for this were also added.
See Trac ticket #180 for more details:
http://bind10.isc.org/ticket/180
Added:
trunk/src/bin/bind10/tests/args_test.py
Modified:
trunk/src/bin/bind10/bind10.py.in
trunk/src/bin/bind10/tests/bind10_test.in
Modified: trunk/src/bin/bind10/bind10.py.in
==============================================================================
--- trunk/src/bin/bind10/bind10.py.in (original)
+++ trunk/src/bin/bind10/bind10.py.in Tue Jun 29 19:54:04 2010
@@ -57,6 +57,9 @@
import select
import random
from optparse import OptionParser, OptionValueError
+import io
+import pwd
+import posix
import isc.cc
@@ -108,21 +111,38 @@
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 @@
# 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 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 @@
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 @@
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 @@
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)
@@ -271,23 +298,6 @@
self.ccs.start()
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
@@ -307,6 +317,28 @@
self.processes[auth.pid] = auth
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']
@@ -324,7 +356,8 @@
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 @@
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 @@
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 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 @@
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 @@
# 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 @@
# shutdown
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
boss_of_bind.shutdown()
+ sys.exit(0)
if __name__ == "__main__":
main()
Modified: trunk/src/bin/bind10/tests/bind10_test.in
==============================================================================
--- trunk/src/bin/bind10/tests/bind10_test.in (original)
+++ trunk/src/bin/bind10/tests/bind10_test.in Tue Jun 29 19:54:04 2010
@@ -27,5 +27,6 @@
export PYTHONPATH
cd ${BIND10_PATH}/tests
-exec ${PYTHON_EXEC} -O bind10_test.py $*
+${PYTHON_EXEC} -O bind10_test.py $*
+exec ${PYTHON_EXEC} -O args_test.py $*
More information about the bind10-changes
mailing list