commit 881ae570ebbc486376ec1fe36488804c93a7976e Author: Damian Johnson atagar@torproject.org Date: Tue Jul 26 18:20:52 2011 -0700
Scripts for overriding system wide torrc
Editing scripts made by Jake for overriding a root owned system wide torrc with an alternate made by arm. For more information see: https://trac.torproject.org/projects/tor/ticket/3629 --- src/resources/torrc-override/override.c | 50 ++++++ src/resources/torrc-override/override.h | 1 + src/resources/torrc-override/override.py | 265 ++++++++++++++++++++++++++++++ 3 files changed, 316 insertions(+), 0 deletions(-)
diff --git a/src/resources/torrc-override/override.c b/src/resources/torrc-override/override.c new file mode 100644 index 0000000..b989584 --- /dev/null +++ b/src/resources/torrc-override/override.c @@ -0,0 +1,50 @@ +// +// This is a very small C wrapper that invokes +// $(DESTDIR)/usr/bin/tor-arm-replace-torrc.py to work around setuid scripting +// issues on the Gnu/Linux operating system. +// +// We assume you have installed it as such for GROUP +// "debian-arm" - This should ensure that only members of the GROUP group will +// be allowed to run this program. When run this program will execute the +// $(DESTDIR)/usr/bin/tor-arm-replace-torrc.py program and will run with the +// uid and group as marked by the OS. +// +// Compile it like so: +// +// make +// +// Or by hand like so: +// +// gcc -o tor-arm-replace-torrc tor-arm-replace-torrc.c +// +// Make it useful like so: +// +// chown root:debian-arm tor-arm-replace-torrc +// chmod 04750 tor-arm-replace-torrc +// +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +// +// If you place a user inside of the $GROUP - they are now able to reconfigure +// Tor. This may lead them to do nasty things on your system. If you start Tor +// as root, you should consider that adding a user to $GROUP is similar to +// giving those users root directly. +// +// This program was written simply to help a users who run arm locally and is +// not required if arm is communicating with a remote Tor process. +// +// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// +// + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> +#include "tor-arm-replace-torrc.h" + +int main() +{ + return execve(TOR_ARM_REPLACE_TORRC, NULL, NULL); +} diff --git a/src/resources/torrc-override/override.h b/src/resources/torrc-override/override.h new file mode 100644 index 0000000..65adc9d --- /dev/null +++ b/src/resources/torrc-override/override.h @@ -0,0 +1 @@ +#define TOR_ARM_REPLACE_TORRC HELPER_NAME diff --git a/src/resources/torrc-override/override.py b/src/resources/torrc-override/override.py new file mode 100755 index 0000000..6cfdbc6 --- /dev/null +++ b/src/resources/torrc-override/override.py @@ -0,0 +1,265 @@ +#!/usr/bin/python + +""" +This overwrites the system wide torrc, located at /etc/tor/torrc, with the +contents of the ARM_CONFIG_FILE. The system wide torrc is owned by root so +this will effectively need root permissions and for us to be in GROUP. + +This file is completely unusable until we make a tor-arm user and perform +other prep by running with the '--init' argument. + +After that's done this is meant to either be used by arm automatically after +writing a torrc to ARM_CONFIG_FILE or by users manually. For arm to use this +automatically you'll either need to... + +- compile torrc-override.c, setuid on the binary, and move it to your path + cd /usr/share/arm/resources/torrc-override + make + chown root:tor-arm override + chmod 04750 override + mv override /usr/bin/torrc-override + +- allow passwordless sudo for this script + edit /etc/sudoers and add a line with: + <arm user> ALL= NOPASSWD: /usr/share/arm/resources/torrc-override/override.py + +To perform this manually run: +/usr/share/arm/resources/torrc-override/override.py +pkill -sighup tor +""" + +import os +import sys +import grp +import pwd +import time +import shutil +import tempfile +import signal + +USER = "tor-arm" +GROUP = "tor-arm" +TOR_CONFIG_FILE = "/etc/tor/torrc" +ARM_CONFIG_FILE = "/var/lib/tor-arm/torrc" + +HELP_MSG = """Usage %s [OPTION] + Backup the system wide torrc (%s) and replace it with the + contents of %s. + + --init creates the necessary user and paths + --remove reverts changes made with --init +""" % (os.path.basename(sys.argv[0]), TOR_CONFIG_FILE, ARM_CONFIG_FILE) + +def init(): + """ + Performs system preparation needed for this script to run, adding the tor-arm + user and setting up paths/permissions. + """ + + # the following is just here if we have a custom destination directory (which + # arm doesn't currently account for) + if not os.path.exists("/bin/"): + print "making '/bin'..." + os.mkdir("/bin") + + if not os.path.exists("/var/lib/tor-arm/"): + print "making '/var/lib/tor-arm'..." + os.makedirs("/var/lib/tor-arm") + + if not os.path.exists("/var/lib/tor-arm/torrc"): + print "making '/var/lib/tor-arm/torrc'..." + open("/var/lib/tor-arm/torrc", 'w').close() + + try: gid = grp.getgrnam(GROUP).gr_gid + except KeyError: + print "adding %s group..." % GROUP + os.system("addgroup --quiet --system %s" % GROUP) + gid = grp.getgrnam(GROUP).gr_gid + print " done, gid: %s" % gid + + try: pwd.getpwnam(USER).pw_uid + except KeyError: + print "adding %s user..." % USER + os.system("adduser --quiet --ingroup %s --no-create-home --home /var/lib/tor-arm/ --shell /bin/sh --system %s" % (GROUP, USER)) + uid = pwd.getpwnam(USER).pw_uid + print " done, uid: %s" % uid + + os.chown("/bin", 0, 0) + os.chown("/var/lib/tor-arm", 0, gid) + os.chmod("/var/lib/tor-arm", 0750) + os.chown("/var/lib/tor-arm/torrc", 0, gid) + os.chmod("/var/lib/tor-arm/torrc", 0760) + +def remove(): + """ + Reverts the changes made by init, and also removes the optional + /bin/torrc-override binary if it exists. + """ + + print "removing %s user..." % USER + os.system("deluser --quiet %s" % USER) + + print "removing %s group..." % GROUP + os.system("delgroup --quiet %s" % GROUP) + + # might not exist since this is compiled and placed separately + try: + print "removing '/bin/torrc-override'..." + os.remove("/bin/torrc-override") + except OSError: + print " no such path" + + try: + print "removing '/var/lib/tor-arm'..." + shutil.rmtree("/var/lib/tor-arm/") + except Exception, exc: + print " unsuccessful: %s" % exc + +def replaceTorrc(): + orig_uid = os.getuid() + orig_euid = os.geteuid() + + # the USER and GROUP must exist on this system + try: + dropped_uid = pwd.getpwnam(USER).pw_uid + dropped_gid = grp.getgrnam(GROUP).gr_gid + dropped_euid, dropped_egid = dropped_uid, dropped_gid + except KeyError: + print "tor-arm user and group was not found, have you run this script with '--init'?" + exit(1) + + # if we're actually root, we skip this group check + # root can get away with all of this + if orig_uid != 0: + # check that the user is in GROUP + if not dropped_gid in os.getgroups(): + print "Your user needs to be a member of the %s group for this to work" % GROUP + sys.exit(1) + + # drop to the unprivileged group, and lose the rest of the groups + os.setgid(dropped_gid) + os.setegid(dropped_egid) + os.setresgid(dropped_gid, dropped_egid, dropped_gid) + os.setgroups([dropped_gid]) + + # make a tempfile and write out the contents + try: + tf = tempfile.NamedTemporaryFile(delete=False) # uses mkstemp internally + + # allows our child process to write to tf.name (not only if their uid matches, not their gid) + os.chown(tf.name, dropped_uid, orig_euid) + except: + print "We were unable to make a temporary file" + sys.exit(1) + + fork_pid = os.fork() + + # open the suspect config after we drop privs + # we assume the dropped privs are still enough to write to the tf + if (fork_pid == 0): + signal.signal(signal.SIGCHLD, signal.SIG_IGN) + + # Drop privs forever in the child process + # I believe this drops os.setfsuid os.setfsgid stuff + # Clear all other supplemental groups for dropped_uid + os.setgroups([dropped_gid]) + os.setresgid(dropped_gid, dropped_egid, dropped_gid) + os.setresuid(dropped_uid, dropped_euid, dropped_uid) + os.setgid(dropped_gid) + os.setegid(dropped_egid) + os.setuid(dropped_uid) + os.seteuid(dropped_euid) + + try: + af = open(ARM_CONFIG_FILE) # this is totally unpriv'ed + + # ensure that the fd we opened has the properties we requrie + configStat = os.fstat(af.fileno()) # this happens on the unpriv'ed FD + if configStat.st_gid != dropped_gid: + print "Arm's configuration file (%s) must be owned by the group %s" % (ARM_CONFIG_FILE, GROUP) + sys.exit(1) + + # if everything checks out, we're as safe as we're going to get + armConfig = af.read(1024 * 1024) # limited read but not too limited + af.close() + tf.file.write(armConfig) + tf.flush() + except: + print "Unable to open the arm config as unpriv'ed user" + sys.exit(1) + finally: + tf.close() + sys.exit(0) + else: + # If we're here, we're in the parent waiting for the child's death + # man, unix is really weird... + _, status = os.waitpid(fork_pid, 0) + + if status != 0: + print "The child seems to have failed; exiting!" + tf.close() + sys.exit(1) + + # attempt to verify that the config is OK + if os.path.exists(tf.name): + # construct our SU string + SUSTRING = "su -c 'tor --verify-config -f " + str(tf.name) + "' " + USER + # We raise privs to drop them with 'su' + os.setuid(0) + os.seteuid(0) + os.setgid(0) + os.setegid(0) + # We drop privs here and exec tor to verify it as the dropped_uid + print "Using Tor to verify that arm will not break Tor's config:" + success = os.system(SUSTRING) + if success != 0: + print "Tor says the new configuration file is invalid: %s (%s)" % (ARM_CONFIG_FILE, tf.name) + sys.exit(1) + + # backup the previous tor config + if os.path.exists(TOR_CONFIG_FILE): + try: + backupFilename = "%s_backup_%i" % (TOR_CONFIG_FILE, int(time.time())) + shutil.copy(TOR_CONFIG_FILE, backupFilename) + except IOError, exc: + print "Unable to backup %s (%s)" % (TOR_CONFIG_FILE, exc) + sys.exit(1) + + # overwrites TOR_CONFIG_FILE with ARM_CONFIG_FILE as loaded into tf.name + try: + shutil.copy(tf.name, TOR_CONFIG_FILE) + print "Successfully reconfigured Tor" + except IOError, exc: + print "Unable to copy %s to %s (%s)" % (tf.name, TOR_CONFIG_FILE, exc) + sys.exit(1) + + # unlink our temp file + try: + os.remove(tf.name) + except: + print "Unable to close temp file %s" % tf.name + sys.exit(1) + + sys.exit(0) + +if __name__ == "__main__": + # sanity check that we're on linux + if os.name != "posix": + print "This is a script specifically for configuring Linux" + sys.exit(1) + + # check that we're running effectively as root + if os.geteuid() != 0: + print "This script needs to be run as root" + sys.exit(1) + + if len(sys.argv) < 2: + replaceTorrc() + elif len(sys.argv) == 2 and sys.argv[1] == "--init": + init() + elif len(sys.argv) == 2 and sys.argv[1] == "--remove": + remove() + else: + print HELP_MSG + sys.exit(1) +
tor-commits@lists.torproject.org