#!/usr/bin/env python # Copyright 2010 Harry Bock # See LICENSE for licensing information. from __future__ import with_statement import sys, os import signal, socket, errno import time from twisted.internet import error from TorCtl import TorCtl, TorUtil from torbel import logger, utils from torbel.controller import Controller from torbel.controller import log, config __version__ = "0.1" # Create the TorBEL root logger 'torbel' according to the # config. logger.create_logger("torbel", level = config.log_level, syslog = config.log_syslog, stdout = config.log_stdout, file = config.log_file) # Get the logger associated with this module. log = logger.get_logger("torbel.main") # Set up torbel.TorCtl to use the log level specified # in config.torctl_log_level. All other settings are # inherited from torbel. torctl = logger.get_logger("torbel.TorCtl") torctl.setLevel(config.torctl_log_level) # Assign torbel.TorCtl to TorUtil.plog(). TorUtil.plog_use_logger("torbel.TorCtl") def sighandler(signum, _): """ TorBEL signal handler. """ control = sighandler.controller if signum in (signal.SIGINT, signal.SIGTERM): log.notice("Received SIGINT, closing.") control.close() elif signum == signal.SIGHUP: log.notice("Received SIGHUP, doing nothing.") elif signum == signal.SIGUSR1: log.info("SIGUSR1 received: Updating consensus.") control._update_consensus(control.conn.get_network_status()) elif signum == signal.SIGUSR2: log.info("SIGUSR2 received: Statistics!") time_running = time.time() - control.tests_started log.info("Running for %d days, %d hours, %d minutes.", time_running / (60 * 60 * 24), time_running / (60 * 60), time_running / (60)) log.info("Completed %d tests.", control.tests_completed) log.debug("%s Scheduler stats:", control.scheduler.name) control.scheduler.print_stats() def drop_privs(): """ Drop root privileges, if needed. """ if os.geteuid() == 0: uid, gid = utils.uid_gid_lookup(config.user, config.group) if config.log_file: # chown our logfile so it doesn't stay owned by root. os.chown(config.log_file, uid, gid) log.debug("Changed owner of log files to uid=%d, gid=%d", uid, gid) utils.drop_privileges(uid, gid) log.notice("Dropped root privileges to uid=%d, gid=%d", uid, gid) # Modified from the Django code base (django.utils.daemonize). # Thanks, Django devs! def daemonize(chdir = ".", umask = 022): "Robustly turn into a UNIX daemon, running in chdir." # First fork try: if os.fork() > 0: sys.exit(0) # kill off parent except OSError, e: sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror)) sys.exit(1) # Obtain a new process group. os.setsid() # Change current directory. os.chdir(chdir) # Set default file creation mask. os.umask(umask) # Second fork try: if os.fork() > 0: os._exit(0) except OSError, e: sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror)) os._exit(1) si = open('/dev/null', 'r') #so = open(out_log, 'a+', 0) #se = open(err_log, 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(si.fileno(), sys.stdout.fileno()) os.dup2(si.fileno(), sys.stderr.fileno()) # Set custom file descriptors so that they get proper buffering. #sys.stdout, sys.stderr = so, se def torbel_start(): log.notice("TorBEL v%s starting.", __version__) # Configuration check. try: utils.config_check(config) except utils.ConfigurationError, e: log.error("Configuration error: %s", e.message) return 1 except AttributeError, e: log.error("Configuration error: missing value: %s", e.args[0]) return 1 if config.daemonize: log.info("Daemonizing. See you!") daemonize() # Handle signals. signal.signal(signal.SIGINT, sighandler) signal.signal(signal.SIGTERM, sighandler) signal.signal(signal.SIGHUP, sighandler) signal.signal(signal.SIGUSR1, sighandler) signal.signal(signal.SIGUSR2, sighandler) # If the configuration requests that we record TorCtl debugging # information, open the debug file to pass to Controller.connect(). torctl_debug_fd = None if config.torctl_debug: try: torctl_debug_fd = open(config.torctl_debug_file, "w+") except IOError, e: log.error("Couldn't open requested TorCtl debug file '%s': %s.", config.torctl_debug_file, e.strerror) return 2 do_tests = "notests" not in sys.argv watchdog = "watchdog" in sys.argv try: control = Controller(watchdog = watchdog, tests = do_tests) sighandler.controller = control # (1) initialize the controller connection and tests control.connect(auth_password = config.control_password, torctl_debug_file = torctl_debug_fd) # (2) drop privileges drop_privs() # (3a) run tests... if do_tests: control.run_tests() # (3b) ...or simply block the main thread # if we are not running tests. else: while True: time.sleep(config.export_interval * 60) if control.terminated: break control.export() except Controller.ConnectError: # Currently atagar's patch prints out an appropriate error message, # but IMO it should really raise an exception. For now we don't need # process the error since it is printed to the console. return 1 except error.CannotListenError, e: (err, message) = e.socketError.args log.error("Could not bind to test port %d: %s", e.port, message) if err == errno.EACCES: log.error("Run TorBEL as a user able to bind to privileged ports.") elif err == errno.EADDRNOTAVAIL: if e.interface: log.error("test_bind_ip must be assigned to an active network interface.") log.error("The current value (%s) does not appear to be valid.", config.test_bind_ip) else: log.error("Could not bind to IPADDR_ANY.") log.error("Please check your network settings and TorBEL configuration.") return 1 except socket.error, e: if "Connection refused" in e.args: log.error("Connection refused! Is Tor control port available?") log.error("Socket error, aborting (%s).", e.args) return 1 except IOError, e: log.error("%s", e.strerror) return 2 except TorCtl.ErrorReply, e: if e.status == 515: log.error("%s", e.message) else: log.error("Connection failed: %s", str(e)) return 2 except TorCtl.TorCtlClosed: pass return 0 if __name__ == "__main__": def usage(): print "Usage: %s [torhost [ctlport]]" % sys.argv[0] sys.exit(1) if sys.argv[-1] == "profile": import cProfile log.notice("cProfile enabled.") cProfile.run("torbel_start()") else: ret = torbel_start() log.notice("TorBEL exiting.") logger.stop_logging() sys.exit(ret)