commit 108517dfe2fc7873b468fed1ce35bde29eb4c107
Author: David Fifield <david(a)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(a)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
+
+: