[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [flashproxy/master] Move facilitator files into a subdirectory.
commit 59ced2a86af9414115a594b499f7307bc7821b69
Author: David Fifield <david@xxxxxxxxxxxxxxx>
Date: Thu Aug 30 12:31:50 2012 -0700
Move facilitator files into a subdirectory.
---
Makefile | 3 +-
fac.py | 182 ----------------------
facilitator | 330 ----------------------------------------
facilitator-test | 124 ---------------
facilitator.cgi | 115 --------------
facilitator/Makefile | 17 ++
facilitator/fac.py | 182 ++++++++++++++++++++++
facilitator/facilitator | 330 ++++++++++++++++++++++++++++++++++++++++
facilitator/facilitator-test | 124 +++++++++++++++
facilitator/facilitator.cgi | 115 ++++++++++++++
facilitator/init.d/facilitator | 67 ++++++++
init.d/facilitator | 67 --------
12 files changed, 836 insertions(+), 820 deletions(-)
diff --git a/Makefile b/Makefile
index 3a15e54..78de922 100644
--- a/Makefile
+++ b/Makefile
@@ -10,14 +10,13 @@ all:
install:
mkdir -p $(BINDIR)
- cp -f flashproxy-client flashproxy-reg-http facilitator $(BINDIR)
+ cp -f flashproxy-client flashproxy-reg-http $(BINDIR)
clean:
rm -f *.pyc
rm -rf dist
test:
- ./facilitator-test
./flashproxy-client-test
./flashproxy-test.js
diff --git a/fac.py b/fac.py
deleted file mode 100644
index b9ad435..0000000
--- a/fac.py
+++ /dev/null
@@ -1,182 +0,0 @@
-import re
-import socket
-
-def parse_addr_spec(spec, defhost = None, defport = None, resolve = False):
- """Parse a host:port specification and return a 2-tuple ("host", port) as
- understood by the Python socket functions.
- >>> parse_addr_spec("192.168.0.1:9999")
- ('192.168.0.1', 9999)
-
- If defhost or defport are given, those parts of the specification may be
- omitted; if so, they will be filled in with defaults.
- >>> parse_addr_spec("192.168.0.2:8888", defhost="192.168.0.1", defport=9999)
- ('192.168.0.2', 8888)
- >>> parse_addr_spec(":8888", defhost="192.168.0.1", defport=9999)
- ('192.168.0.1', 9999)
- >>> parse_addr_spec("192.168.0.2:", defhost="192.168.0.1", defport=9999)
- ('192.168.0.2', 9999)
- >>> parse_addr_spec(":", defhost="192.168.0.1", defport=9999)
- ('192.168.0.1', 9999)
-
- If resolve is true, then the host in the specification or the defhost may be
- a domain name, which will be resolved. If resolve is false, then the host
- must be a numeric IPv4 or IPv6 address.
-
- IPv6 addresses must be enclosed in square brackets."""
- host = None
- port = None
- m = None
- # IPv6 syntax.
- if not m:
- m = re.match(ur'^\[(.+)\]:(\d+)$', spec)
- if m:
- host, port = m.groups()
- af = socket.AF_INET6
- if not m:
- m = re.match(ur'^\[(.+)\]:?$', spec)
- if m:
- host, = m.groups()
- af = socket.AF_INET6
- # IPv4 syntax.
- if not m:
- m = re.match(ur'^(.+):(\d+)$', spec)
- if m:
- host, port = m.groups()
- af = socket.AF_INET
- if not m:
- m = re.match(ur'^:?(\d+)$', spec)
- if m:
- port, = m.groups()
- af = 0
- if not m:
- host = spec
- af = 0
- host = host or defhost
- port = port or defport
- if host is None or port is None:
- raise ValueError("Bad address specification \"%s\"" % spec)
-
- # Now we have split around the colon and have a guess at the address family.
- # Forward-resolve the name into an addrinfo struct. Real DNS resolution is
- # done only if resolve is true; otherwise the address must be numeric.
- if resolve:
- flags = 0
- else:
- flags = socket.AI_NUMERICHOST
- try:
- addrs = socket.getaddrinfo(host, port, af, socket.SOCK_STREAM, socket.IPPROTO_TCP, flags)
- except socket.gaierror, e:
- raise ValueError("Bad host or port: \"%s\" \"%s\": %s" % (host, port, str(e)))
- if not addrs:
- raise ValueError("Bad host or port: \"%s\" \"%s\"" % (host, port))
-
- # Convert the result of socket.getaddrinfo (which is a 2-tuple for IPv4 and
- # a 4-tuple for IPv6) into a (host, port) 2-tuple.
- host, port = socket.getnameinfo(addrs[0][4], socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
- port = int(port)
- return host, port
-
-def format_addr(addr):
- host, port = addr
- host_str = u""
- port_str = u""
- if host is not None:
- # Numeric IPv6 address?
- try:
- addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_NUMERICHOST)
- af = addrs[0][0]
- except socket.gaierror, e:
- af = 0
- if af == socket.AF_INET6:
- host_str = u"[%s]" % host
- else:
- host_str = u"%s" % host
- if port is not None:
- if not (0 < port <= 65535):
- raise ValueError("port must be between 1 and 65535 (is %d)" % port)
- port_str = u":%d" % port
-
- if not host_str and not port_str:
- raise ValueError("host and port may not both be None")
- return u"%s%s" % (host_str, port_str)
-
-def skip_space(pos, line):
- """Skip a (possibly empty) sequence of space characters (the ASCII character
- '\x20' exactly). Returns a pair (pos, num_skipped)."""
- begin = pos
- while pos < len(line) and line[pos] == "\x20":
- pos += 1
- return pos, pos - begin
-
-TOKEN_CHARS = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
-def get_token(pos, line):
- begin = pos
- while pos < len(line) and line[pos] in TOKEN_CHARS:
- pos += 1
- if begin == pos:
- raise ValueError("No token found at position %d" % pos)
- return pos, line[begin:pos]
-
-def get_quoted_string(pos, line):
- chars = []
- if not (pos < len(line) and line[pos] == '"'):
- raise ValueError("Expected '\"' at beginning of quoted string.")
- pos += 1
- while pos < len(line) and line[pos] != '"':
- if line[pos] == '\\':
- pos += 1
- if not (pos < len(line)):
- raise ValueError("End of line after backslash in quoted string")
- chars.append(line[pos])
- pos += 1
- if not (pos < len(line) and line[pos] == '"'):
- raise ValueError("Expected '\"' at end of quoted string.")
- pos += 1
- return pos, "".join(chars)
-
-def parse_transaction(line):
- """A transaction is a command followed by zero or more key-value pairs. Like so:
- COMMAND KEY="VALUE" KEY="\"ESCAPED\" VALUE"
- Values must be quoted. Any byte value may be escaped with a backslash.
- Returns a pair: (COMMAND, ((KEY1, VALUE1), (KEY2, VALUE2), ...)).
- """
- pos = 0
- pos, skipped = skip_space(pos, line)
- pos, command = get_token(pos, line)
-
- pairs = []
- while True:
- pos, skipped = skip_space(pos, line)
- if not (pos < len(line)):
- break
- if skipped == 0:
- raise ValueError("Expected space before key-value pair")
- pos, key = get_token(pos, line)
- if not (pos < len(line) and line[pos] == '='):
- raise ValueError("No '=' found after key")
- pos += 1
- pos, value = get_quoted_string(pos, line)
- pairs.append((key, value))
- return command, tuple(pairs)
-
-def param_first(key, params):
- for k, v in params:
- if key == k:
- return v
- return None
-
-def quote_string(s):
- chars = []
- for c in s:
- if c == "\\":
- c = "\\\\"
- elif c == "\"":
- c = "\\\""
- chars.append(c)
- return "\"" + "".join(chars) + "\""
-
-def render_transaction(command, *params):
- parts = [command]
- for key, value in params:
- parts.append("%s=%s" % (key, quote_string(value)))
- return " ".join(parts)
diff --git a/facilitator b/facilitator
deleted file mode 100755
index 3695b52..0000000
--- a/facilitator
+++ /dev/null
@@ -1,330 +0,0 @@
-#!/usr/bin/env python
-
-import SocketServer
-import errno
-import getopt
-import os
-import socket
-import sys
-import threading
-import time
-
-import fac
-
-LISTEN_ADDRESS = "127.0.0.1"
-DEFAULT_LISTEN_PORT = 9002
-DEFAULT_RELAY_PORT = 9001
-DEFAULT_LOG_FILENAME = "facilitator.log"
-
-# Don't indulge clients for more than this many seconds.
-CLIENT_TIMEOUT = 1.0
-# Buffer no many than this many bytes when trying to read a line.
-READLINE_MAX_LENGTH = 10240
-
-LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
-
-class options(object):
- listen_port = DEFAULT_LISTEN_PORT
- log_filename = DEFAULT_LOG_FILENAME
- log_file = sys.stdout
- relay_spec = None
- daemonize = True
- pid_filename = None
- safe_logging = True
-
- @staticmethod
- def set_relay_spec(spec):
- spec = fac.parse_addr_spec(spec, defport = DEFAULT_RELAY_PORT, resolve = True)
- options.relay_spec = fac.format_addr(spec)
-
-def usage(f = sys.stdout):
- print >> f, """\
-Usage: %(progname)s -r RELAY <OPTIONS>
-Flash proxy facilitator: Register client addresses and serve them out
-again. Listen on 127.0.0.1 and port PORT (by default %(port)d).
- -d, --debug don't daemonize, log to stdout.
- -h, --help show this help.
- -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.
- -r, --relay RELAY send RELAY (host:port) to proxies as the relay to use.
- --unsafe-logging don't scrub IP addresses from logs.\
-""" % {
- "progname": sys.argv[0],
- "port": DEFAULT_LISTEN_PORT,
- "log": DEFAULT_LOG_FILENAME,
-}
-
-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()
-
-class TCPReg(object):
- def __init__(self, host, port):
- self.host = host
- self.port = port
-
- def __unicode__(self):
- return fac.format_addr((self.host, self.port))
-
- def __str__(self):
- return unicode(self).encode("UTF-8")
-
- def __cmp__(self, other):
- if isinstance(other, TCPReg):
- return cmp((self.host, self.port), (other.host, other.port))
- else:
- return False
-
-class Reg(object):
- @staticmethod
- def parse(spec, defhost = None, defport = None):
- host, port = fac.parse_addr_spec(spec, defhost, defport)
- return TCPReg(host, port)
-
-class RegSet(object):
- def __init__(self):
- self.set = []
- self.cv = threading.Condition()
-
- def add(self, reg):
- self.cv.acquire()
- try:
- if reg not in list(self.set):
- self.set.append(reg)
- self.cv.notify()
- return True
- else:
- return False
- finally:
- self.cv.release()
-
- def fetch(self):
- self.cv.acquire()
- try:
- if not self.set:
- return None
- return self.set.pop(0)
- finally:
- self.cv.release()
-
- def __len__(self):
- self.cv.acquire()
- try:
- return len(self.set)
- finally:
- self.cv.release()
-
-# A decorator to ignore "broken pipe" errors.
-def catch_epipe(fn):
- def ret(self, *args):
- try:
- return fn(self, *args)
- except socket.error, e:
- try:
- err_num = e.errno
- except AttributeError:
- # Before Python 2.6, exception can be a pair.
- err_num, errstr = e
- except:
- raise
- if err_num != errno.EPIPE:
- raise
- return ret
-
-class Handler(SocketServer.StreamRequestHandler):
- def __init__(self, *args, **kwargs):
- self.deadline = time.time() + CLIENT_TIMEOUT
- # Buffer for readline.
- 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 readline(self):
- # A line already buffered?
- i = self.buffer.find("\n")
- if i >= 0:
- line = self.buffer[:i+1]
- self.buffer = self.buffer[i+1:]
- return line
-
- auxbuf = []
- buflen = len(self.buffer)
- while True:
- data = self.recv()
- if not data:
- if self.buffer or auxbuf:
- raise socket.error("readline: stream does not end with a newline")
- else:
- return ""
- i = data.find("\n")
- if i >= 0:
- line = self.buffer + "".join(auxbuf) + data[:i+1]
- self.buffer = data[i+1:]
- return line
- else:
- auxbuf.append(data)
- buflen += len(data)
- if buflen >= READLINE_MAX_LENGTH:
- raise socket.error("readline: refusing to buffer %d bytes (last read was %d bytes)" % (buflen, len(data)))
-
- @catch_epipe
- def handle(self):
- num_lines = 0
- while True:
- try:
- line = self.readline()
- if not line:
- break
- num_lines += 1
- except socket.error, e:
- log("socket error after reading %d lines: %s" % (num_lines, str(e)))
- break
- if not self.handle_line(line):
- break
-
- def handle_line(self, line):
- if not (len(line) > 0 and line[-1] == '\n'):
- raise ValueError("No newline at end of string returned by readline")
- try:
- command, params = fac.parse_transaction(line[:-1])
- except ValueError, e:
- log("fac.parse_transaction: %s" % e)
- self.send_error()
- return False
-
- if command == "GET":
- return self.do_GET(params)
- if command == "PUT":
- return self.do_PUT(params)
- else:
- self.send_error()
- return False
-
- def send_ok(self):
- print >> self.wfile, "OK"
-
- def send_error(self):
- print >> self.wfile, "ERROR"
-
- def do_GET(self, params):
- reg = REGS.fetch()
- if reg:
- log(u"proxy gets %s, relay %s (now %d)" %
- (safe_str(unicode(reg)), options.relay_spec, len(REGS)))
- print >> self.wfile, fac.render_transaction("OK", ("CLIENT", str(reg)), ("RELAY", options.relay_spec))
- else:
- log(u"proxy gets none")
- print >> self.wfile, fac.render_transaction("NONE")
- return True
-
- def do_PUT(self, params):
- client_spec = fac.param_first("CLIENT", params)
- if client_spec is None:
- log(u"PUT missing CLIENT param")
- self.send_error()
- return False
-
- # FROM
-
- try:
- reg = Reg.parse(client_spec, self.client_address[0])
- except ValueError, e:
- log(u"syntax error in %s: %s" % (safe_str(repr(client_spec)), repr(str(e))))
- self.send_error()
- return False
-
- if REGS.add(reg):
- log(u"client %s (now %d)" % (safe_str(unicode(reg)), len(REGS)))
- else:
- log(u"client %s (already present, now %d)" % (safe_str(unicode(reg)), len(REGS)))
-
- self.send_ok()
- return True
-
- finish = catch_epipe(SocketServer.StreamRequestHandler.finish)
-
-class Server(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
- allow_reuse_address = True
-
-REGS = RegSet()
-
-def main():
- opts, args = getopt.gnu_getopt(sys.argv[1:], "dhl:p:r:",
- ["debug", "help", "log=", "port=", "pidfile=", "relay=", "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 == "-l" or o == "--log":
- options.log_filename = a
- elif o == "-p" or o == "--port":
- options.listen_port = int(a)
- elif o == "--pidfile":
- options.pid_filename = a
- elif o == "-r" or o == "--relay":
- try:
- options.set_relay_spec(a)
- except socket.gaierror, e:
- print >> sys.stderr, u"Can't resolve relay %s: %s" % (repr(a), str(e))
- sys.exit(1)
- elif o == "--unsafe-logging":
- options.safe_logging = False
-
- if not options.relay_spec:
- print >> sys.stderr, """\
-The -r option is required. Give it the relay that will be sent to proxies.
- -r HOST[:PORT]\
- """
- sys.exit(1)
-
- 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]))
- log(u"using relay address %s" % options.relay_spec)
-
- 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-test b/facilitator-test
deleted file mode 100755
index b06f5d7..0000000
--- a/facilitator-test
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/usr/bin/env python
-
-import socket
-import subprocess
-import time
-import unittest
-
-import fac
-
-FACILITATOR_HOST = "127.0.0.1"
-FACILITATOR_PORT = 9002
-
-def gimme_socket(host, port):
- addrinfo = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP)[0]
- s = socket.socket(addrinfo[0], addrinfo[1], addrinfo[2])
- s.settimeout(10.0)
- s.connect(addrinfo[4])
- return s
-
-class FacilitatorTest(unittest.TestCase):
- def gimme_socket(self):
- return gimme_socket(FACILITATOR_HOST, FACILITATOR_PORT)
-
- def setUp(self):
- self.process = subprocess.Popen(["./facilitator", "-d", "-p", str(FACILITATOR_PORT), "-r", "0.0.1.0:1", "-l", "/dev/null"])
- time.sleep(0.1)
-
- def tearDown(self):
- self.process.terminate()
-
- def test_timeout(self):
- """Test that the socket will not accept slow writes indefinitely.
- Successive sends should not reset the timeout counter."""
- s = self.gimme_socket()
- time.sleep(0.3)
- s.send("w")
- time.sleep(0.3)
- s.send("w")
- time.sleep(0.3)
- s.send("w")
- time.sleep(0.3)
- s.send("w")
- time.sleep(0.3)
- self.assertRaises(socket.error, s.send, "w")
-
- def test_readline_limit(self):
- """Test that reads won't buffer indefinitely."""
- s = self.gimme_socket()
- buflen = 0
- try:
- while buflen + 1024 < 200000:
- s.send("X" * 1024)
- buflen += 1024
- self.fail("should have raised a socket error")
- except socket.error:
- pass
-
-# def test_same_proxy(self):
-# """Test that the same proxy doesn't get the same client when asking
-# twice."""
-# self.fail()
-#
-# def test_num_clients(self):
-# """Test that the same proxy can pick up up to five different clients but
-# no more. Test that a proxy ceasing to handle a client allows the proxy
-# to handle another, different client."""
-# self.fail()
-#
-# def test_num_proxies(self):
-# """Test that a single client is handed out to five different proxies but
-# no more. Test that a proxy ceasing to handle a client reduces its count
-# so another proxy can handle it."""
-# self.fail()
-#
-# def test_proxy_timeout(self):
-# """Test that a proxy ceasing to connect for some time period causes that
-# proxy's clients to be unhandled by that proxy."""
-# self.fail()
-#
-# def test_localhost_only(self):
-# """Test that the facilitator doesn't listen on any external
-# addresses."""
-# self.fail()
-#
-# def test_hostname(self):
-# """Test that the facilitator rejects hostnames."""
-# self.fail()
-
-class ParseTransactionTest(unittest.TestCase):
- def test_empty_string(self):
- self.assertRaises(ValueError, fac.parse_transaction, "")
-
- def test_correct(self):
- self.assertEqual(fac.parse_transaction("COMMAND"), ("COMMAND", ()))
- self.assertEqual(fac.parse_transaction("COMMAND X=\"\""), ("COMMAND", (("X", ""),)))
- self.assertEqual(fac.parse_transaction("COMMAND X=\"ABC\""), ("COMMAND", (("X", "ABC"),)))
- self.assertEqual(fac.parse_transaction("COMMAND X=\"\\A\\B\\C\""), ("COMMAND", (("X", "ABC"),)))
- self.assertEqual(fac.parse_transaction("COMMAND X=\"\\\\\\\"\""), ("COMMAND", (("X", "\\\""),)))
- self.assertEqual(fac.parse_transaction("COMMAND X=\"ABC\" Y=\"DEF\""), ("COMMAND", (("X", "ABC"), ("Y", "DEF"))))
- self.assertEqual(fac.parse_transaction("COMMAND KEY-NAME=\"ABC\""), ("COMMAND", (("KEY-NAME", "ABC"),)))
- self.assertEqual(fac.parse_transaction("COMMAND KEY_NAME=\"ABC\""), ("COMMAND", (("KEY_NAME", "ABC"),)))
-
- def test_missing_command(self):
- self.assertRaises(ValueError, fac.parse_transaction, "X=\"ABC\"")
- self.assertRaises(ValueError, fac.parse_transaction, " X=\"ABC\"")
-
- def test_missing_space(self):
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND/X=\"ABC\"")
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC\"Y=\"DEF\"")
-
- def test_bad_quotes(self):
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"")
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC")
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC\" Y=\"ABC")
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC\\")
-
- def test_truncated(self):
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=")
-
- def test_newline(self):
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC\" \nY=\"DEF\"")
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/facilitator.cgi b/facilitator.cgi
deleted file mode 100755
index 39566d3..0000000
--- a/facilitator.cgi
+++ /dev/null
@@ -1,115 +0,0 @@
-#!/usr/bin/env python
-
-import cgi
-import os
-import socket
-import sys
-import urllib
-
-import fac
-
-FACILITATOR_ADDR = ("127.0.0.1", 9002)
-
-def exit_error(status):
- print """\
-Status: %d\r
-\r""" % status
- sys.exit()
-
-def fac_socket():
- return socket.create_connection(FACILITATOR_ADDR, 1.0).makefile()
-
-def transact(f, command, *params):
- transaction = fac.render_transaction(command, *params)
- print >> f, transaction
- f.flush()
- line = f.readline()
- if not (len(line) > 0 and line[-1] == '\n'):
- raise ValueError("No newline at end of string returned by facilitator")
- return fac.parse_transaction(line[:-1])
-
-def put_reg(client_addr, registrant_addr):
- f = fac_socket()
- try:
- command, params = transact(f, "PUT", ("CLIENT", fac.format_addr(client_addr)), ("FROM", fac.format_addr(registrant_addr)))
- finally:
- f.close()
- if command == "OK":
- pass
- else:
- exit_error(500)
-
-def get_reg(proxy_addr):
- f = fac_socket()
- try:
- command, params = transact(f, "GET", ("FROM", fac.format_addr(proxy_addr)))
- finally:
- f.close()
- if command == "NONE":
- return {
- "client": ""
- }
- elif command == "OK":
- client_spec = fac.param_first("CLIENT", params)
- relay_spec = fac.param_first("RELAY", params)
- if not client_spec or not relay_spec:
- exit_error(500)
- try:
- # Check the syntax returned by the backend.
- client = fac.parse_addr_spec(client_spec)
- relay = fac.parse_addr_spec(relay_spec)
- except ValueError:
- exit_error(500)
- return {
- "client": fac.format_addr(client),
- "relay": fac.format_addr(relay),
- }
- else:
- exit_error(500)
-
-method = os.environ.get("REQUEST_METHOD")
-proxy_addr = (os.environ.get("REMOTE_ADDR"), None)
-
-if not method or not proxy_addr[0]:
- exit_error(400)
-
-fs = cgi.FieldStorage()
-
-def do_get():
- try:
- reg = get_reg(proxy_addr) or ""
- except:
- exit_error(500)
- # Allow XMLHttpRequest from any domain. http://www.w3.org/TR/cors/.
- print """\
-Status: 200\r
-Content-Type: application/x-www-form-urlencoded\r
-Cache-Control: no-cache\r
-Access-Control-Allow-Origin: *\r
-\r"""
- sys.stdout.write(urllib.urlencode(reg))
-
-def do_post():
- client_specs = fs.getlist("client")
- if len(client_specs) != 1:
- exit_error(400)
- client_spec = client_specs[0]
- try:
- client_addr = fac.parse_addr_spec(client_spec, defhost=proxy_addr[0])
- except ValueError:
- exit_error(400)
- try:
- put_reg(client_addr, proxy_addr)
- except:
- raise
- exit_error(500)
- print """\
-Status: 200\r
-\r"""
-
-if method == "GET":
- do_get()
-elif method == "POST":
- do_post()
-else:
- exit_error(405)
diff --git a/facilitator/Makefile b/facilitator/Makefile
new file mode 100644
index 0000000..1d9cb54
--- /dev/null
+++ b/facilitator/Makefile
@@ -0,0 +1,17 @@
+PREFIX = /usr/local
+BINDIR = $(PREFIX)/bin
+
+all:
+ :
+
+install:
+ mkdir -p $(BINDIR)
+ cp -f facilitator facilitator.cgi fac.py $(BINDIR)
+
+clean:
+ rm -f *.pyc
+
+test:
+ ./facilitator-test
+
+.PHONY: all install clean test
diff --git a/facilitator/fac.py b/facilitator/fac.py
new file mode 100644
index 0000000..b9ad435
--- /dev/null
+++ b/facilitator/fac.py
@@ -0,0 +1,182 @@
+import re
+import socket
+
+def parse_addr_spec(spec, defhost = None, defport = None, resolve = False):
+ """Parse a host:port specification and return a 2-tuple ("host", port) as
+ understood by the Python socket functions.
+ >>> parse_addr_spec("192.168.0.1:9999")
+ ('192.168.0.1', 9999)
+
+ If defhost or defport are given, those parts of the specification may be
+ omitted; if so, they will be filled in with defaults.
+ >>> parse_addr_spec("192.168.0.2:8888", defhost="192.168.0.1", defport=9999)
+ ('192.168.0.2', 8888)
+ >>> parse_addr_spec(":8888", defhost="192.168.0.1", defport=9999)
+ ('192.168.0.1', 9999)
+ >>> parse_addr_spec("192.168.0.2:", defhost="192.168.0.1", defport=9999)
+ ('192.168.0.2', 9999)
+ >>> parse_addr_spec(":", defhost="192.168.0.1", defport=9999)
+ ('192.168.0.1', 9999)
+
+ If resolve is true, then the host in the specification or the defhost may be
+ a domain name, which will be resolved. If resolve is false, then the host
+ must be a numeric IPv4 or IPv6 address.
+
+ IPv6 addresses must be enclosed in square brackets."""
+ host = None
+ port = None
+ m = None
+ # IPv6 syntax.
+ if not m:
+ m = re.match(ur'^\[(.+)\]:(\d+)$', spec)
+ if m:
+ host, port = m.groups()
+ af = socket.AF_INET6
+ if not m:
+ m = re.match(ur'^\[(.+)\]:?$', spec)
+ if m:
+ host, = m.groups()
+ af = socket.AF_INET6
+ # IPv4 syntax.
+ if not m:
+ m = re.match(ur'^(.+):(\d+)$', spec)
+ if m:
+ host, port = m.groups()
+ af = socket.AF_INET
+ if not m:
+ m = re.match(ur'^:?(\d+)$', spec)
+ if m:
+ port, = m.groups()
+ af = 0
+ if not m:
+ host = spec
+ af = 0
+ host = host or defhost
+ port = port or defport
+ if host is None or port is None:
+ raise ValueError("Bad address specification \"%s\"" % spec)
+
+ # Now we have split around the colon and have a guess at the address family.
+ # Forward-resolve the name into an addrinfo struct. Real DNS resolution is
+ # done only if resolve is true; otherwise the address must be numeric.
+ if resolve:
+ flags = 0
+ else:
+ flags = socket.AI_NUMERICHOST
+ try:
+ addrs = socket.getaddrinfo(host, port, af, socket.SOCK_STREAM, socket.IPPROTO_TCP, flags)
+ except socket.gaierror, e:
+ raise ValueError("Bad host or port: \"%s\" \"%s\": %s" % (host, port, str(e)))
+ if not addrs:
+ raise ValueError("Bad host or port: \"%s\" \"%s\"" % (host, port))
+
+ # Convert the result of socket.getaddrinfo (which is a 2-tuple for IPv4 and
+ # a 4-tuple for IPv6) into a (host, port) 2-tuple.
+ host, port = socket.getnameinfo(addrs[0][4], socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
+ port = int(port)
+ return host, port
+
+def format_addr(addr):
+ host, port = addr
+ host_str = u""
+ port_str = u""
+ if host is not None:
+ # Numeric IPv6 address?
+ try:
+ addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_NUMERICHOST)
+ af = addrs[0][0]
+ except socket.gaierror, e:
+ af = 0
+ if af == socket.AF_INET6:
+ host_str = u"[%s]" % host
+ else:
+ host_str = u"%s" % host
+ if port is not None:
+ if not (0 < port <= 65535):
+ raise ValueError("port must be between 1 and 65535 (is %d)" % port)
+ port_str = u":%d" % port
+
+ if not host_str and not port_str:
+ raise ValueError("host and port may not both be None")
+ return u"%s%s" % (host_str, port_str)
+
+def skip_space(pos, line):
+ """Skip a (possibly empty) sequence of space characters (the ASCII character
+ '\x20' exactly). Returns a pair (pos, num_skipped)."""
+ begin = pos
+ while pos < len(line) and line[pos] == "\x20":
+ pos += 1
+ return pos, pos - begin
+
+TOKEN_CHARS = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
+def get_token(pos, line):
+ begin = pos
+ while pos < len(line) and line[pos] in TOKEN_CHARS:
+ pos += 1
+ if begin == pos:
+ raise ValueError("No token found at position %d" % pos)
+ return pos, line[begin:pos]
+
+def get_quoted_string(pos, line):
+ chars = []
+ if not (pos < len(line) and line[pos] == '"'):
+ raise ValueError("Expected '\"' at beginning of quoted string.")
+ pos += 1
+ while pos < len(line) and line[pos] != '"':
+ if line[pos] == '\\':
+ pos += 1
+ if not (pos < len(line)):
+ raise ValueError("End of line after backslash in quoted string")
+ chars.append(line[pos])
+ pos += 1
+ if not (pos < len(line) and line[pos] == '"'):
+ raise ValueError("Expected '\"' at end of quoted string.")
+ pos += 1
+ return pos, "".join(chars)
+
+def parse_transaction(line):
+ """A transaction is a command followed by zero or more key-value pairs. Like so:
+ COMMAND KEY="VALUE" KEY="\"ESCAPED\" VALUE"
+ Values must be quoted. Any byte value may be escaped with a backslash.
+ Returns a pair: (COMMAND, ((KEY1, VALUE1), (KEY2, VALUE2), ...)).
+ """
+ pos = 0
+ pos, skipped = skip_space(pos, line)
+ pos, command = get_token(pos, line)
+
+ pairs = []
+ while True:
+ pos, skipped = skip_space(pos, line)
+ if not (pos < len(line)):
+ break
+ if skipped == 0:
+ raise ValueError("Expected space before key-value pair")
+ pos, key = get_token(pos, line)
+ if not (pos < len(line) and line[pos] == '='):
+ raise ValueError("No '=' found after key")
+ pos += 1
+ pos, value = get_quoted_string(pos, line)
+ pairs.append((key, value))
+ return command, tuple(pairs)
+
+def param_first(key, params):
+ for k, v in params:
+ if key == k:
+ return v
+ return None
+
+def quote_string(s):
+ chars = []
+ for c in s:
+ if c == "\\":
+ c = "\\\\"
+ elif c == "\"":
+ c = "\\\""
+ chars.append(c)
+ return "\"" + "".join(chars) + "\""
+
+def render_transaction(command, *params):
+ parts = [command]
+ for key, value in params:
+ parts.append("%s=%s" % (key, quote_string(value)))
+ return " ".join(parts)
diff --git a/facilitator/facilitator b/facilitator/facilitator
new file mode 100755
index 0000000..3695b52
--- /dev/null
+++ b/facilitator/facilitator
@@ -0,0 +1,330 @@
+#!/usr/bin/env python
+
+import SocketServer
+import errno
+import getopt
+import os
+import socket
+import sys
+import threading
+import time
+
+import fac
+
+LISTEN_ADDRESS = "127.0.0.1"
+DEFAULT_LISTEN_PORT = 9002
+DEFAULT_RELAY_PORT = 9001
+DEFAULT_LOG_FILENAME = "facilitator.log"
+
+# Don't indulge clients for more than this many seconds.
+CLIENT_TIMEOUT = 1.0
+# Buffer no many than this many bytes when trying to read a line.
+READLINE_MAX_LENGTH = 10240
+
+LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
+
+class options(object):
+ listen_port = DEFAULT_LISTEN_PORT
+ log_filename = DEFAULT_LOG_FILENAME
+ log_file = sys.stdout
+ relay_spec = None
+ daemonize = True
+ pid_filename = None
+ safe_logging = True
+
+ @staticmethod
+ def set_relay_spec(spec):
+ spec = fac.parse_addr_spec(spec, defport = DEFAULT_RELAY_PORT, resolve = True)
+ options.relay_spec = fac.format_addr(spec)
+
+def usage(f = sys.stdout):
+ print >> f, """\
+Usage: %(progname)s -r RELAY <OPTIONS>
+Flash proxy facilitator: Register client addresses and serve them out
+again. Listen on 127.0.0.1 and port PORT (by default %(port)d).
+ -d, --debug don't daemonize, log to stdout.
+ -h, --help show this help.
+ -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.
+ -r, --relay RELAY send RELAY (host:port) to proxies as the relay to use.
+ --unsafe-logging don't scrub IP addresses from logs.\
+""" % {
+ "progname": sys.argv[0],
+ "port": DEFAULT_LISTEN_PORT,
+ "log": DEFAULT_LOG_FILENAME,
+}
+
+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()
+
+class TCPReg(object):
+ def __init__(self, host, port):
+ self.host = host
+ self.port = port
+
+ def __unicode__(self):
+ return fac.format_addr((self.host, self.port))
+
+ def __str__(self):
+ return unicode(self).encode("UTF-8")
+
+ def __cmp__(self, other):
+ if isinstance(other, TCPReg):
+ return cmp((self.host, self.port), (other.host, other.port))
+ else:
+ return False
+
+class Reg(object):
+ @staticmethod
+ def parse(spec, defhost = None, defport = None):
+ host, port = fac.parse_addr_spec(spec, defhost, defport)
+ return TCPReg(host, port)
+
+class RegSet(object):
+ def __init__(self):
+ self.set = []
+ self.cv = threading.Condition()
+
+ def add(self, reg):
+ self.cv.acquire()
+ try:
+ if reg not in list(self.set):
+ self.set.append(reg)
+ self.cv.notify()
+ return True
+ else:
+ return False
+ finally:
+ self.cv.release()
+
+ def fetch(self):
+ self.cv.acquire()
+ try:
+ if not self.set:
+ return None
+ return self.set.pop(0)
+ finally:
+ self.cv.release()
+
+ def __len__(self):
+ self.cv.acquire()
+ try:
+ return len(self.set)
+ finally:
+ self.cv.release()
+
+# A decorator to ignore "broken pipe" errors.
+def catch_epipe(fn):
+ def ret(self, *args):
+ try:
+ return fn(self, *args)
+ except socket.error, e:
+ try:
+ err_num = e.errno
+ except AttributeError:
+ # Before Python 2.6, exception can be a pair.
+ err_num, errstr = e
+ except:
+ raise
+ if err_num != errno.EPIPE:
+ raise
+ return ret
+
+class Handler(SocketServer.StreamRequestHandler):
+ def __init__(self, *args, **kwargs):
+ self.deadline = time.time() + CLIENT_TIMEOUT
+ # Buffer for readline.
+ 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 readline(self):
+ # A line already buffered?
+ i = self.buffer.find("\n")
+ if i >= 0:
+ line = self.buffer[:i+1]
+ self.buffer = self.buffer[i+1:]
+ return line
+
+ auxbuf = []
+ buflen = len(self.buffer)
+ while True:
+ data = self.recv()
+ if not data:
+ if self.buffer or auxbuf:
+ raise socket.error("readline: stream does not end with a newline")
+ else:
+ return ""
+ i = data.find("\n")
+ if i >= 0:
+ line = self.buffer + "".join(auxbuf) + data[:i+1]
+ self.buffer = data[i+1:]
+ return line
+ else:
+ auxbuf.append(data)
+ buflen += len(data)
+ if buflen >= READLINE_MAX_LENGTH:
+ raise socket.error("readline: refusing to buffer %d bytes (last read was %d bytes)" % (buflen, len(data)))
+
+ @catch_epipe
+ def handle(self):
+ num_lines = 0
+ while True:
+ try:
+ line = self.readline()
+ if not line:
+ break
+ num_lines += 1
+ except socket.error, e:
+ log("socket error after reading %d lines: %s" % (num_lines, str(e)))
+ break
+ if not self.handle_line(line):
+ break
+
+ def handle_line(self, line):
+ if not (len(line) > 0 and line[-1] == '\n'):
+ raise ValueError("No newline at end of string returned by readline")
+ try:
+ command, params = fac.parse_transaction(line[:-1])
+ except ValueError, e:
+ log("fac.parse_transaction: %s" % e)
+ self.send_error()
+ return False
+
+ if command == "GET":
+ return self.do_GET(params)
+ if command == "PUT":
+ return self.do_PUT(params)
+ else:
+ self.send_error()
+ return False
+
+ def send_ok(self):
+ print >> self.wfile, "OK"
+
+ def send_error(self):
+ print >> self.wfile, "ERROR"
+
+ def do_GET(self, params):
+ reg = REGS.fetch()
+ if reg:
+ log(u"proxy gets %s, relay %s (now %d)" %
+ (safe_str(unicode(reg)), options.relay_spec, len(REGS)))
+ print >> self.wfile, fac.render_transaction("OK", ("CLIENT", str(reg)), ("RELAY", options.relay_spec))
+ else:
+ log(u"proxy gets none")
+ print >> self.wfile, fac.render_transaction("NONE")
+ return True
+
+ def do_PUT(self, params):
+ client_spec = fac.param_first("CLIENT", params)
+ if client_spec is None:
+ log(u"PUT missing CLIENT param")
+ self.send_error()
+ return False
+
+ # FROM
+
+ try:
+ reg = Reg.parse(client_spec, self.client_address[0])
+ except ValueError, e:
+ log(u"syntax error in %s: %s" % (safe_str(repr(client_spec)), repr(str(e))))
+ self.send_error()
+ return False
+
+ if REGS.add(reg):
+ log(u"client %s (now %d)" % (safe_str(unicode(reg)), len(REGS)))
+ else:
+ log(u"client %s (already present, now %d)" % (safe_str(unicode(reg)), len(REGS)))
+
+ self.send_ok()
+ return True
+
+ finish = catch_epipe(SocketServer.StreamRequestHandler.finish)
+
+class Server(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
+ allow_reuse_address = True
+
+REGS = RegSet()
+
+def main():
+ opts, args = getopt.gnu_getopt(sys.argv[1:], "dhl:p:r:",
+ ["debug", "help", "log=", "port=", "pidfile=", "relay=", "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 == "-l" or o == "--log":
+ options.log_filename = a
+ elif o == "-p" or o == "--port":
+ options.listen_port = int(a)
+ elif o == "--pidfile":
+ options.pid_filename = a
+ elif o == "-r" or o == "--relay":
+ try:
+ options.set_relay_spec(a)
+ except socket.gaierror, e:
+ print >> sys.stderr, u"Can't resolve relay %s: %s" % (repr(a), str(e))
+ sys.exit(1)
+ elif o == "--unsafe-logging":
+ options.safe_logging = False
+
+ if not options.relay_spec:
+ print >> sys.stderr, """\
+The -r option is required. Give it the relay that will be sent to proxies.
+ -r HOST[:PORT]\
+ """
+ sys.exit(1)
+
+ 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]))
+ log(u"using relay address %s" % options.relay_spec)
+
+ 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/facilitator-test b/facilitator/facilitator-test
new file mode 100755
index 0000000..b06f5d7
--- /dev/null
+++ b/facilitator/facilitator-test
@@ -0,0 +1,124 @@
+#!/usr/bin/env python
+
+import socket
+import subprocess
+import time
+import unittest
+
+import fac
+
+FACILITATOR_HOST = "127.0.0.1"
+FACILITATOR_PORT = 9002
+
+def gimme_socket(host, port):
+ addrinfo = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP)[0]
+ s = socket.socket(addrinfo[0], addrinfo[1], addrinfo[2])
+ s.settimeout(10.0)
+ s.connect(addrinfo[4])
+ return s
+
+class FacilitatorTest(unittest.TestCase):
+ def gimme_socket(self):
+ return gimme_socket(FACILITATOR_HOST, FACILITATOR_PORT)
+
+ def setUp(self):
+ self.process = subprocess.Popen(["./facilitator", "-d", "-p", str(FACILITATOR_PORT), "-r", "0.0.1.0:1", "-l", "/dev/null"])
+ time.sleep(0.1)
+
+ def tearDown(self):
+ self.process.terminate()
+
+ def test_timeout(self):
+ """Test that the socket will not accept slow writes indefinitely.
+ Successive sends should not reset the timeout counter."""
+ s = self.gimme_socket()
+ time.sleep(0.3)
+ s.send("w")
+ time.sleep(0.3)
+ s.send("w")
+ time.sleep(0.3)
+ s.send("w")
+ time.sleep(0.3)
+ s.send("w")
+ time.sleep(0.3)
+ self.assertRaises(socket.error, s.send, "w")
+
+ def test_readline_limit(self):
+ """Test that reads won't buffer indefinitely."""
+ s = self.gimme_socket()
+ buflen = 0
+ try:
+ while buflen + 1024 < 200000:
+ s.send("X" * 1024)
+ buflen += 1024
+ self.fail("should have raised a socket error")
+ except socket.error:
+ pass
+
+# def test_same_proxy(self):
+# """Test that the same proxy doesn't get the same client when asking
+# twice."""
+# self.fail()
+#
+# def test_num_clients(self):
+# """Test that the same proxy can pick up up to five different clients but
+# no more. Test that a proxy ceasing to handle a client allows the proxy
+# to handle another, different client."""
+# self.fail()
+#
+# def test_num_proxies(self):
+# """Test that a single client is handed out to five different proxies but
+# no more. Test that a proxy ceasing to handle a client reduces its count
+# so another proxy can handle it."""
+# self.fail()
+#
+# def test_proxy_timeout(self):
+# """Test that a proxy ceasing to connect for some time period causes that
+# proxy's clients to be unhandled by that proxy."""
+# self.fail()
+#
+# def test_localhost_only(self):
+# """Test that the facilitator doesn't listen on any external
+# addresses."""
+# self.fail()
+#
+# def test_hostname(self):
+# """Test that the facilitator rejects hostnames."""
+# self.fail()
+
+class ParseTransactionTest(unittest.TestCase):
+ def test_empty_string(self):
+ self.assertRaises(ValueError, fac.parse_transaction, "")
+
+ def test_correct(self):
+ self.assertEqual(fac.parse_transaction("COMMAND"), ("COMMAND", ()))
+ self.assertEqual(fac.parse_transaction("COMMAND X=\"\""), ("COMMAND", (("X", ""),)))
+ self.assertEqual(fac.parse_transaction("COMMAND X=\"ABC\""), ("COMMAND", (("X", "ABC"),)))
+ self.assertEqual(fac.parse_transaction("COMMAND X=\"\\A\\B\\C\""), ("COMMAND", (("X", "ABC"),)))
+ self.assertEqual(fac.parse_transaction("COMMAND X=\"\\\\\\\"\""), ("COMMAND", (("X", "\\\""),)))
+ self.assertEqual(fac.parse_transaction("COMMAND X=\"ABC\" Y=\"DEF\""), ("COMMAND", (("X", "ABC"), ("Y", "DEF"))))
+ self.assertEqual(fac.parse_transaction("COMMAND KEY-NAME=\"ABC\""), ("COMMAND", (("KEY-NAME", "ABC"),)))
+ self.assertEqual(fac.parse_transaction("COMMAND KEY_NAME=\"ABC\""), ("COMMAND", (("KEY_NAME", "ABC"),)))
+
+ def test_missing_command(self):
+ self.assertRaises(ValueError, fac.parse_transaction, "X=\"ABC\"")
+ self.assertRaises(ValueError, fac.parse_transaction, " X=\"ABC\"")
+
+ def test_missing_space(self):
+ self.assertRaises(ValueError, fac.parse_transaction, "COMMAND/X=\"ABC\"")
+ self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC\"Y=\"DEF\"")
+
+ def test_bad_quotes(self):
+ self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"")
+ self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC")
+ self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC\" Y=\"ABC")
+ self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC\\")
+
+ def test_truncated(self):
+ self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=")
+
+ def test_newline(self):
+ self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC\" \nY=\"DEF\"")
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/facilitator/facilitator.cgi b/facilitator/facilitator.cgi
new file mode 100755
index 0000000..39566d3
--- /dev/null
+++ b/facilitator/facilitator.cgi
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+
+import cgi
+import os
+import socket
+import sys
+import urllib
+
+import fac
+
+FACILITATOR_ADDR = ("127.0.0.1", 9002)
+
+def exit_error(status):
+ print """\
+Status: %d\r
+\r""" % status
+ sys.exit()
+
+def fac_socket():
+ return socket.create_connection(FACILITATOR_ADDR, 1.0).makefile()
+
+def transact(f, command, *params):
+ transaction = fac.render_transaction(command, *params)
+ print >> f, transaction
+ f.flush()
+ line = f.readline()
+ if not (len(line) > 0 and line[-1] == '\n'):
+ raise ValueError("No newline at end of string returned by facilitator")
+ return fac.parse_transaction(line[:-1])
+
+def put_reg(client_addr, registrant_addr):
+ f = fac_socket()
+ try:
+ command, params = transact(f, "PUT", ("CLIENT", fac.format_addr(client_addr)), ("FROM", fac.format_addr(registrant_addr)))
+ finally:
+ f.close()
+ if command == "OK":
+ pass
+ else:
+ exit_error(500)
+
+def get_reg(proxy_addr):
+ f = fac_socket()
+ try:
+ command, params = transact(f, "GET", ("FROM", fac.format_addr(proxy_addr)))
+ finally:
+ f.close()
+ if command == "NONE":
+ return {
+ "client": ""
+ }
+ elif command == "OK":
+ client_spec = fac.param_first("CLIENT", params)
+ relay_spec = fac.param_first("RELAY", params)
+ if not client_spec or not relay_spec:
+ exit_error(500)
+ try:
+ # Check the syntax returned by the backend.
+ client = fac.parse_addr_spec(client_spec)
+ relay = fac.parse_addr_spec(relay_spec)
+ except ValueError:
+ exit_error(500)
+ return {
+ "client": fac.format_addr(client),
+ "relay": fac.format_addr(relay),
+ }
+ else:
+ exit_error(500)
+
+method = os.environ.get("REQUEST_METHOD")
+proxy_addr = (os.environ.get("REMOTE_ADDR"), None)
+
+if not method or not proxy_addr[0]:
+ exit_error(400)
+
+fs = cgi.FieldStorage()
+
+def do_get():
+ try:
+ reg = get_reg(proxy_addr) or ""
+ except:
+ exit_error(500)
+ # Allow XMLHttpRequest from any domain. http://www.w3.org/TR/cors/.
+ print """\
+Status: 200\r
+Content-Type: application/x-www-form-urlencoded\r
+Cache-Control: no-cache\r
+Access-Control-Allow-Origin: *\r
+\r"""
+ sys.stdout.write(urllib.urlencode(reg))
+
+def do_post():
+ client_specs = fs.getlist("client")
+ if len(client_specs) != 1:
+ exit_error(400)
+ client_spec = client_specs[0]
+ try:
+ client_addr = fac.parse_addr_spec(client_spec, defhost=proxy_addr[0])
+ except ValueError:
+ exit_error(400)
+ try:
+ put_reg(client_addr, proxy_addr)
+ except:
+ raise
+ exit_error(500)
+ print """\
+Status: 200\r
+\r"""
+
+if method == "GET":
+ do_get()
+elif method == "POST":
+ do_post()
+else:
+ exit_error(405)
diff --git a/facilitator/init.d/facilitator b/facilitator/init.d/facilitator
new file mode 100755
index 0000000..5510d11
--- /dev/null
+++ b/facilitator/init.d/facilitator
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# facilitator This shell script takes care of starting and stopping
+# the flash proxy facilitator.
+#
+# chkconfig: 2345 90 10
+# description: Flash proxy facilitator.
+# processname: facilitator
+# pidfile: /var/flashproxy/facilitator.pid
+
+# Installation instructions:
+# cp facilitator /etc/init.d/facilitator
+# chkconfig --add facilitator
+# service facilitator start
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+# Replace this with the address of a Tor relay with a websocket pluggable
+# transport. You can use host:port syntax.
+RELAY=tor1.bamsoftware.com:9901
+
+BINDIR=/usr/local/bin
+VARDIR=/var/flashproxy
+PROG=$BINDIR/facilitator
+PIDFILE=$VARDIR/facilitator.pid
+USER=flashproxy
+
+# See how we were called.
+case "$1" in
+ start)
+ [ -x $PROG ] || exit 1
+ echo -n $"Starting flash proxy facilitator: "
+ cd $VARDIR && daemon --user $USER --pidfile $PIDFILE $PROG --pidfile $PIDFILE -r $RELAY
+ RETVAL=$?
+ echo
+ [ $RETVAL -eq 0 ] && touch /var/lock/subsys/facilitator
+ ;;
+ stop)
+ # Stop daemon.
+ echo -n $"Shutting down flash proxy facilitator: "
+ killproc -p $PIDFILE
+ RETVAL=$?
+ echo
+ if [ $RETVAL -eq 0 ]; then
+ rm -f /var/lock/subsys/facilitator
+ rm -f $PIDFILE
+ fi
+ ;;
+ status)
+ status -p $PIDFILE facilitator
+ RETVAL=$?
+ ;;
+ restart|reload)
+ $0 stop
+ $0 start
+ ;;
+ condrestart)
+ [ -f /var/lock/subsys/facilitator ] && restart || :
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|status|restart}"
+ RETVAL=3
+ ;;
+esac
+
+exit $RETVAL
diff --git a/init.d/facilitator b/init.d/facilitator
deleted file mode 100755
index 5510d11..0000000
--- a/init.d/facilitator
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/bin/sh
-#
-# facilitator This shell script takes care of starting and stopping
-# the flash proxy facilitator.
-#
-# chkconfig: 2345 90 10
-# description: Flash proxy facilitator.
-# processname: facilitator
-# pidfile: /var/flashproxy/facilitator.pid
-
-# Installation instructions:
-# cp facilitator /etc/init.d/facilitator
-# chkconfig --add facilitator
-# service facilitator start
-
-# Source function library.
-. /etc/rc.d/init.d/functions
-
-# Replace this with the address of a Tor relay with a websocket pluggable
-# transport. You can use host:port syntax.
-RELAY=tor1.bamsoftware.com:9901
-
-BINDIR=/usr/local/bin
-VARDIR=/var/flashproxy
-PROG=$BINDIR/facilitator
-PIDFILE=$VARDIR/facilitator.pid
-USER=flashproxy
-
-# See how we were called.
-case "$1" in
- start)
- [ -x $PROG ] || exit 1
- echo -n $"Starting flash proxy facilitator: "
- cd $VARDIR && daemon --user $USER --pidfile $PIDFILE $PROG --pidfile $PIDFILE -r $RELAY
- RETVAL=$?
- echo
- [ $RETVAL -eq 0 ] && touch /var/lock/subsys/facilitator
- ;;
- stop)
- # Stop daemon.
- echo -n $"Shutting down flash proxy facilitator: "
- killproc -p $PIDFILE
- RETVAL=$?
- echo
- if [ $RETVAL -eq 0 ]; then
- rm -f /var/lock/subsys/facilitator
- rm -f $PIDFILE
- fi
- ;;
- status)
- status -p $PIDFILE facilitator
- RETVAL=$?
- ;;
- restart|reload)
- $0 stop
- $0 start
- ;;
- condrestart)
- [ -f /var/lock/subsys/facilitator ] && restart || :
- ;;
- *)
- echo $"Usage: $0 {start|stop|status|restart}"
- RETVAL=3
- ;;
-esac
-
-exit $RETVAL
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits