commit 108517dfe2fc7873b468fed1ce35bde29eb4c107 Author: David Fifield david@bamsoftware.com Date: Wed Mar 6 23:43:22 2013 -0800
facilitator-reg-daemon.
This process will be the one with access to key material. --- doc/facilitator-howto.txt | 2 + facilitator/Makefile | 4 +- facilitator/facilitator-reg-daemon | 214 +++++++++++++++++++++++++++++ facilitator/init.d/facilitator-reg-daemon | 118 ++++++++++++++++ 4 files changed, 336 insertions(+), 2 deletions(-)
diff --git a/doc/facilitator-howto.txt b/doc/facilitator-howto.txt index fda9b8c..25c0a8c 100644 --- a/doc/facilitator-howto.txt +++ b/doc/facilitator-howto.txt @@ -107,6 +107,8 @@ and fac.py to /usr/local/bin. It also installs System V init files to # /etc/init.d/facilitator start # update-rc.d facilitator-email-poller defaults # /etc/init.d/facilitator-email-poller start + # update-rc.d facilitator-reg-daemon defaults + # /etc/init.d/facilitator-reg-daemon start
== HTTP server setup
diff --git a/facilitator/Makefile b/facilitator/Makefile index 14d7807..d23a259 100644 --- a/facilitator/Makefile +++ b/facilitator/Makefile @@ -6,8 +6,8 @@ all:
install: mkdir -p $(BINDIR) - cp -f facilitator facilitator-email-poller facilitator.cgi fac.py $(BINDIR) - cp -f init.d/facilitator init.d/facilitator-email-poller /etc/init.d/ + cp -f facilitator facilitator-email-poller facilitator-reg-daemon facilitator.cgi fac.py $(BINDIR) + cp -f init.d/facilitator init.d/facilitator-email-poller init.d/facilitator-reg-daemon /etc/init.d/
clean: rm -f *.pyc diff --git a/facilitator/facilitator-reg-daemon b/facilitator/facilitator-reg-daemon new file mode 100755 index 0000000..996c50e --- /dev/null +++ b/facilitator/facilitator-reg-daemon @@ -0,0 +1,214 @@ +#!/usr/bin/env python + +import SocketServer +import getopt +import os +import socket +import stat +import sys +import threading +import time + +import fac + +from M2Crypto import RSA + +# Generating an RSA keypair for use by this program: +# openssl genrsa reg-daemon 2048 +# chmod 600 reg-daemon + +LISTEN_ADDRESS = "127.0.0.1" +DEFAULT_LISTEN_PORT = 9003 +FACILITATOR_ADDR = ("127.0.0.1", 9002) +DEFAULT_LOG_FILENAME = "facilitator-reg-daemon.log" + +# Don't indulge clients for more than this many seconds. +CLIENT_TIMEOUT = 1.0 +# Buffer no more than this many bytes per connection. +MAX_LENGTH = 40 * 1024 + +LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" + +class options(object): + key_filename = None + listen_port = DEFAULT_LISTEN_PORT + log_filename = DEFAULT_LOG_FILENAME + log_file = sys.stdout + daemonize = True + pid_filename = None + safe_logging = True + +def usage(f = sys.stdout): + print >> f, """\ +Usage: %(progname)s --key=KEYFILE +Facilitator-side daemon that reads base64-encoded encrypted client +registrations and registers them with a local facilitator. This program +exists on its own in order to isolate the reading of key material in a +single process. + + -d, --debug don't daemonize, log to stdout. + -h, --help show this help. + -k, --key=KEYFILE read the private key from KEYFILE (required). + -l, --log FILENAME write log to FILENAME (default "%(log)s"). + -p, --port PORT listen on PORT (by default %(port)d). + --pidfile FILENAME write PID to FILENAME after daemonizing. + --unsafe-logging don't scrub email password and IP addresses from logs.\ +""" % { + "progname": sys.argv[0], + "log": DEFAULT_LOG_FILENAME, + "port": DEFAULT_LISTEN_PORT, +} + +def safe_str(s): + """Return s if options.safe_logging is true, and "[scrubbed]" otherwise.""" + if options.safe_logging: + return "[scrubbed]" + else: + return s + +log_lock = threading.Lock() +def log(msg): + log_lock.acquire() + try: + print >> options.log_file, (u"%s %s" % (time.strftime(LOG_DATE_FORMAT), msg)).encode("UTF-8") + options.log_file.flush() + finally: + log_lock.release() + +def find_client_addr(body): + """Find and parse the first client line of the form + client=... + Returns None if no client line was found.""" + for line in body.splitlines(): + if line.startswith("client="): + _, client_spec = line.split("=", 1) + return fac.parse_addr_spec(client_spec) + return None + +# Return true iff the given fd is readable, writable, and executable only by its +# owner. +def check_perms(fd): + mode = os.fstat(fd)[0] + return (mode & (stat.S_IRWXG | stat.S_IRWXO)) == 0 + +class Handler(SocketServer.StreamRequestHandler): + def __init__(self, *args, **kwargs): + self.deadline = time.time() + CLIENT_TIMEOUT + self.buffer = "" + SocketServer.StreamRequestHandler.__init__(self, *args, **kwargs) + + def recv(self): + timeout = self.deadline - time.time() + self.connection.settimeout(timeout) + return self.connection.recv(1024) + + def read_input(self): + while True: + data = self.recv() + if not data: + break + self.buffer += data + if len(self.buffer) > MAX_LENGTH: + raise socket.error("refusing to buffer %d bytes (last read was %d bytes)" % (buflen, len(data))) + return self.buffer + + @fac.catch_epipe + def handle(self): + try: + b64_ciphertext = self.read_input() + except socket.error, e: + log("socket error reading input: %s" % str(e)) + return + try: + ciphertext = b64_ciphertext.decode("base64") + plaintext = rsa.private_decrypt(ciphertext, RSA.pkcs1_oaep_padding) + client_addr = find_client_addr(plaintext) + log(u"registering %s" % safe_str(fac.format_addr(client_addr))) + if fac.put_reg(FACILITATOR_ADDR, client_addr): + print >> self.wfile, "OK" + else: + print >> self.wfile, "FAIL" + except Exception, e: + log("error registering: %s" % str(e)) + print >> self.wfile, "FAIL" + raise + + finish = fac.catch_epipe(SocketServer.StreamRequestHandler.finish) + +class Server(SocketServer.ThreadingMixIn, SocketServer.TCPServer): + allow_reuse_address = True + +def main(): + global rsa + + opts, args = getopt.gnu_getopt(sys.argv[1:], "dhk:l:p:", ["debug", "help", "key=", "log=", "port=", "pidfile=", "unsafe-logging"]) + for o, a in opts: + if o == "-d" or o == "--debug": + options.daemonize = False + options.log_filename = None + elif o == "-h" or o == "--help": + usage() + sys.exit() + elif o == "-k" or o == "--key": + options.key_filename = a + elif o == "-l" or o == "--log": + options.log_filename = a + elif o == "-p" or o == "--pass": + options.listen_port = int(a) + elif o == "--pidfile": + options.pid_filename = a + elif o == "--unsafe-logging": + options.safe_logging = False + + if len(args) != 0: + usage(sys.stderr) + sys.exit(1) + + # Load the private key. + if options.key_filename is None: + print >> sys.stderr, "The --key option is required." + sys.exit(1) + try: + key_file = open(options.key_filename) + except Exception, e: + print >> sys.stderr, "Failed to open private key file "%s": %s." % (options.key_filename, str(e)) + sys.exit(1) + try: + if not check_perms(key_file.fileno()): + print >> sys.stderr, "Refusing to run with group- or world-readable private key file. Try" + print >> sys.stderr, "\tchmod 600 %s" % options.key_filename + sys.exit(1) + rsa = RSA.load_key_string(key_file.read()) + finally: + key_file.close() + + if options.log_filename: + options.log_file = open(options.log_filename, "a") + # Send error tracebacks to the log. + sys.stderr = options.log_file + else: + options.log_file = sys.stdout + + addrinfo = socket.getaddrinfo(LISTEN_ADDRESS, options.listen_port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP)[0] + + server = Server(addrinfo[4], Handler) + + log(u"start on %s" % fac.format_addr(addrinfo[4])) + + if options.daemonize: + log(u"daemonizing") + pid = os.fork() + if pid != 0: + if options.pid_filename: + f = open(options.pid_filename, "w") + print >> f, pid + f.close() + sys.exit(0) + + try: + server.serve_forever() + except KeyboardInterrupt: + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/facilitator/init.d/facilitator-reg-daemon b/facilitator/init.d/facilitator-reg-daemon new file mode 100755 index 0000000..52d4890 --- /dev/null +++ b/facilitator/init.d/facilitator-reg-daemon @@ -0,0 +1,118 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: facilitator-reg-daemon +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Flash proxy local registration daemon. +# Description: Debian init script for the flash proxy local registration daemon. +### END INIT INFO +# +# Author: David Fifield david@bamsoftware.com +# + +# Based on /etc/init.d/skeleton from Debian 6. + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="Flash proxy local registration daemon" +NAME=facilitator-reg-daemon +PIDFILE=/var/run/$NAME.pid +LOGFILE=/var/log/$NAME.log +CONFDIR=/etc/flashproxy +DAEMON=/usr/local/bin/$NAME +DAEMON_ARGS="--key $CONFDIR/reg-daemon.key --log $LOGFILE --pidfile $PIDFILE" +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +. /lib/init/vars.sh +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + rm -f $PIDFILE + return "$RETVAL" +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + restart|force-reload) + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +:
tor-commits@lists.torproject.org