[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] r10838: put geoff goodell's tarball into svn as a 'blossom' module (/ blossom blossom/trunk blossom/trunk/flags blossom/trunk/icons)



Author: arma
Date: 2007-07-16 03:09:49 -0400 (Mon, 16 Jul 2007)
New Revision: 10838

Added:
   blossom/
   blossom/branches/
   blossom/tags/
   blossom/trunk/
   blossom/trunk/Makefile
   blossom/trunk/TorCtl.py
   blossom/trunk/TorCtl0.py
   blossom/trunk/TorCtl1.py
   blossom/trunk/blossom.pl
   blossom/trunk/blossom.py
   blossom/trunk/country-codes.txt
   blossom/trunk/exit.pl
   blossom/trunk/flags/
   blossom/trunk/flags/19.gif
   blossom/trunk/flags/33.gif
   blossom/trunk/flags/af.gif
   blossom/trunk/flags/al.gif
   blossom/trunk/flags/am.gif
   blossom/trunk/flags/an.gif
   blossom/trunk/flags/ao.gif
   blossom/trunk/flags/ar.gif
   blossom/trunk/flags/at.gif
   blossom/trunk/flags/au.gif
   blossom/trunk/flags/aw.gif
   blossom/trunk/flags/az.gif
   blossom/trunk/flags/ba.gif
   blossom/trunk/flags/bb.gif
   blossom/trunk/flags/bd.gif
   blossom/trunk/flags/be.gif
   blossom/trunk/flags/bf.gif
   blossom/trunk/flags/bg.gif
   blossom/trunk/flags/bh.gif
   blossom/trunk/flags/bi.gif
   blossom/trunk/flags/bj.gif
   blossom/trunk/flags/bm.gif
   blossom/trunk/flags/bn.gif
   blossom/trunk/flags/bo.gif
   blossom/trunk/flags/br.gif
   blossom/trunk/flags/bs.gif
   blossom/trunk/flags/bt.gif
   blossom/trunk/flags/bw.gif
   blossom/trunk/flags/by.gif
   blossom/trunk/flags/bz.gif
   blossom/trunk/flags/ca.gif
   blossom/trunk/flags/cf.gif
   blossom/trunk/flags/cg.gif
   blossom/trunk/flags/ch.gif
   blossom/trunk/flags/ci.gif
   blossom/trunk/flags/ck.gif
   blossom/trunk/flags/cl.gif
   blossom/trunk/flags/cm.gif
   blossom/trunk/flags/cn.gif
   blossom/trunk/flags/co.gif
   blossom/trunk/flags/cr.gif
   blossom/trunk/flags/cu.gif
   blossom/trunk/flags/cv.gif
   blossom/trunk/flags/cy.gif
   blossom/trunk/flags/cz.gif
   blossom/trunk/flags/de.gif
   blossom/trunk/flags/dk.gif
   blossom/trunk/flags/dz.gif
   blossom/trunk/flags/ec.gif
   blossom/trunk/flags/ee.gif
   blossom/trunk/flags/eg.gif
   blossom/trunk/flags/er.gif
   blossom/trunk/flags/es.gif
   blossom/trunk/flags/et.gif
   blossom/trunk/flags/fi.gif
   blossom/trunk/flags/fj.gif
   blossom/trunk/flags/fo.gif
   blossom/trunk/flags/fr.gif
   blossom/trunk/flags/ga.gif
   blossom/trunk/flags/gb.gif
   blossom/trunk/flags/ge.gif
   blossom/trunk/flags/gi.gif
   blossom/trunk/flags/gl.gif
   blossom/trunk/flags/gp.gif
   blossom/trunk/flags/gr.gif
   blossom/trunk/flags/gt.gif
   blossom/trunk/flags/gu.gif
   blossom/trunk/flags/gy.gif
   blossom/trunk/flags/hk.gif
   blossom/trunk/flags/hr.gif
   blossom/trunk/flags/ht.gif
   blossom/trunk/flags/hu.gif
   blossom/trunk/flags/id.gif
   blossom/trunk/flags/ie.gif
   blossom/trunk/flags/il.gif
   blossom/trunk/flags/in.gif
   blossom/trunk/flags/iq.gif
   blossom/trunk/flags/ir.gif
   blossom/trunk/flags/is.gif
   blossom/trunk/flags/it.gif
   blossom/trunk/flags/jm.gif
   blossom/trunk/flags/jo.gif
   blossom/trunk/flags/jp.gif
   blossom/trunk/flags/ke.gif
   blossom/trunk/flags/kg.gif
   blossom/trunk/flags/kh.gif
   blossom/trunk/flags/ki.gif
   blossom/trunk/flags/kp.gif
   blossom/trunk/flags/kr.gif
   blossom/trunk/flags/ky.gif
   blossom/trunk/flags/kz.gif
   blossom/trunk/flags/lb.gif
   blossom/trunk/flags/lc.gif
   blossom/trunk/flags/lk.gif
   blossom/trunk/flags/lt.gif
   blossom/trunk/flags/lu.gif
   blossom/trunk/flags/lv.gif
   blossom/trunk/flags/ly.gif
   blossom/trunk/flags/ma.gif
   blossom/trunk/flags/mc.gif
   blossom/trunk/flags/md.gif
   blossom/trunk/flags/mg.gif
   blossom/trunk/flags/mn.gif
   blossom/trunk/flags/mo.gif
   blossom/trunk/flags/mp.gif
   blossom/trunk/flags/ms.gif
   blossom/trunk/flags/mt.gif
   blossom/trunk/flags/mx.gif
   blossom/trunk/flags/my.gif
   blossom/trunk/flags/mz.gif
   blossom/trunk/flags/na.gif
   blossom/trunk/flags/nc.gif
   blossom/trunk/flags/nf.gif
   blossom/trunk/flags/nl.gif
   blossom/trunk/flags/no.gif
   blossom/trunk/flags/np.gif
   blossom/trunk/flags/nr.gif
   blossom/trunk/flags/nz.gif
   blossom/trunk/flags/om.gif
   blossom/trunk/flags/pa.gif
   blossom/trunk/flags/pe.gif
   blossom/trunk/flags/pf.gif
   blossom/trunk/flags/ph.gif
   blossom/trunk/flags/pk.gif
   blossom/trunk/flags/pl.gif
   blossom/trunk/flags/pm.gif
   blossom/trunk/flags/pr.gif
   blossom/trunk/flags/pt.gif
   blossom/trunk/flags/py.gif
   blossom/trunk/flags/qa.gif
   blossom/trunk/flags/ro.gif
   blossom/trunk/flags/ru.gif
   blossom/trunk/flags/sa.gif
   blossom/trunk/flags/sb.gif
   blossom/trunk/flags/sd.gif
   blossom/trunk/flags/se.gif
   blossom/trunk/flags/sg.gif
   blossom/trunk/flags/si.gif
   blossom/trunk/flags/sk.gif
   blossom/trunk/flags/sl.gif
   blossom/trunk/flags/sm.gif
   blossom/trunk/flags/so.gif
   blossom/trunk/flags/sy.gif
   blossom/trunk/flags/tc.gif
   blossom/trunk/flags/tg.gif
   blossom/trunk/flags/th.gif
   blossom/trunk/flags/tn.gif
   blossom/trunk/flags/to.gif
   blossom/trunk/flags/tp.gif
   blossom/trunk/flags/tr.gif
   blossom/trunk/flags/tt.gif
   blossom/trunk/flags/tv.gif
   blossom/trunk/flags/tw.gif
   blossom/trunk/flags/tz.gif
   blossom/trunk/flags/ua.gif
   blossom/trunk/flags/ug.gif
   blossom/trunk/flags/us.gif
   blossom/trunk/flags/uy.gif
   blossom/trunk/flags/va.gif
   blossom/trunk/flags/ve.gif
   blossom/trunk/flags/vg.gif
   blossom/trunk/flags/vi.gif
   blossom/trunk/flags/vn.gif
   blossom/trunk/flags/ws.gif
   blossom/trunk/flags/ye.gif
   blossom/trunk/flags/yu.gif
   blossom/trunk/flags/za.gif
   blossom/trunk/flags/zw.gif
   blossom/trunk/flags/~~.gif
   blossom/trunk/icons/
   blossom/trunk/icons/bx.gif
   blossom/trunk/icons/hn.gif
   blossom/trunk/icons/s0.gif
   blossom/trunk/icons/s1.gif
   blossom/trunk/icons/ur.gif
   blossom/trunk/icons/v0.gif
   blossom/trunk/icons/v1.gif
   blossom/trunk/icons/v2.gif
   blossom/trunk/icons/v3.gif
   blossom/trunk/package-name
   blossom/trunk/style.css
   blossom/trunk/tor-resolve-server.pl
Log:
put geoff goodell's tarball into svn as a 'blossom' module


Added: blossom/trunk/Makefile
===================================================================
--- blossom/trunk/Makefile	                        (rev 0)
+++ blossom/trunk/Makefile	2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,24 @@
+TARGET_DIR=~/.blossom
+
+all:
+	rm -rf ${TARGET_DIR}
+	mkdir ${TARGET_DIR}
+	cp -r ./country-codes.txt ./flags ./icons ./style.css ${TARGET_DIR}
+
+dist: clean
+	mkdir blossom
+	mkdir blossom/flags
+	mkdir blossom/icons
+	cp *.* blossom
+	rm -f blossom/*.bz2
+	cp Makefile blossom
+	cp package-name blossom
+	cp flags/*.* blossom/flags
+	cp icons/*.* blossom/icons
+	tar cvjf `./package-name` blossom
+	/bin/rm -rf blossom
+
+clean:
+	/bin/rm -rf blossom *~ *.pyc
+
+FORCE:

Added: blossom/trunk/TorCtl.py
===================================================================
--- blossom/trunk/TorCtl.py	                        (rev 0)
+++ blossom/trunk/TorCtl.py	2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,536 @@
+#!/usr/bin/python
+# TorCtl.py -- Python module to interface with Tor Control interface.
+# Copyright 2005 Nick Mathewson -- See LICENSE for licensing information.
+#$Id: TorCtl.py,v 1.13 2005/11/19 19:42:31 nickm Exp $
+
+"""
+TorCtl -- Library to control Tor processes.  See TorCtlDemo.py for example use.
+"""
+
+import os
+import re
+import struct
+import sys
+import threading
+import Queue
+
+class TorCtlError(Exception):
+    "Generic error raised by TorControl code."
+    pass
+
+class TorCtlClosed(TorCtlError):
+    "Raised when the controller connection is closed by Tor (not by us.)"
+    pass
+
+class ProtocolError(TorCtlError):
+    "Raised on violations in Tor controller protocol"
+    pass
+
+class ErrorReply(TorCtlError):
+    "Raised when Tor controller returns an error"
+    pass
+
+class EventHandler:
+    """An 'EventHandler' wraps callbacks for the events Tor can return."""
+    def __init__(self):
+        """Create a new EventHandler."""
+        from TorCtl0 import EVENT_TYPE
+        self._map0 = {
+            EVENT_TYPE.CIRCSTATUS : self.circ_status,
+            EVENT_TYPE.STREAMSTATUS : self.stream_status,
+            EVENT_TYPE.ORCONNSTATUS : self.or_conn_status,
+            EVENT_TYPE.BANDWIDTH : self.bandwidth,
+            EVENT_TYPE.NEWDESC : self.new_desc,
+            EVENT_TYPE.DEBUG_MSG : self.msg,
+            EVENT_TYPE.INFO_MSG : self.msg,
+            EVENT_TYPE.NOTICE_MSG : self.msg,
+            EVENT_TYPE.WARN_MSG : self.msg,
+            EVENT_TYPE.ERR_MSG : self.msg,
+            }
+        self._map1 = {
+            "CIRC" : self.circ_status,
+            "STREAM" : self.stream_status,
+            "ORCONN" : self.or_conn_status,
+            "BW" : self.bandwidth,
+            "DEBUG" : self.msg,
+            "INFO" : self.msg,
+            "NOTICE" : self.msg,
+            "WARN" : self.msg,
+            "ERR" : self.msg,
+            "NEWDESC" : self.new_desc,
+            "ADDRMAP" : self.address_mapped
+            }
+
+    def handle0(self, evbody):
+        """Dispatcher: called from Connection when an event is received."""
+        evtype, args = self.decode0(evbody)
+        self._map0.get(evtype, self.unknown_event)(evtype, *args)
+
+    def decode0(self, body):
+        """Unpack an event message into a type/arguments-tuple tuple."""
+        if len(body)<2:
+            raise ProtocolError("EVENT body too short.")
+        evtype, = struct.unpack("!H", body[:2])
+        body = body[2:]
+        if evtype == EVENT_TYPE.CIRCSTATUS:
+            if len(body)<5:
+                raise ProtocolError("CIRCUITSTATUS event too short.")
+            status,ident = struct.unpack("!BL", body[:5])
+            path = _unterminate(body[5:]).split(",")
+            args = status, ident, path
+        elif evtype == EVENT_TYPE.STREAMSTATUS:
+            if len(body)<5:
+                raise ProtocolError("STREAMSTATUS event too short.")
+            status,ident = struct.unpack("!BL", body[:5])
+            target = _unterminate(body[5:])
+            args = status, ident, target
+        elif evtype == EVENT_TYPE.ORCONNSTATUS:
+            if len(body)<2:
+                raise ProtocolError("ORCONNSTATUS event too short.")
+            status = ord(body[0])
+            target = _unterminate(body[1:])
+            args = status, target
+        elif evtype == EVENT_TYPE.BANDWIDTH:
+            if len(body)<8:
+                raise ProtocolError("BANDWIDTH event too short.")
+            read, written = struct.unpack("!LL",body[:8])
+            args = read, written
+        elif evtype == EVENT_TYPE.OBSOLETE_LOG:
+            args = (_unterminate(body),)
+        elif evtype == EVENT_TYPE.NEWDESC:
+            args = (_unterminate(body).split(","),)
+        elif EVENT_TYPE.DEBUG_MSG <= evtype <= EVENT_TYPE.ERR_MSG:
+            args = (EVENT_TYPE.nameOf[evtype], _unterminate(body))
+        else:
+            args = (body,)
+
+        return evtype, args
+
+    def handle1(self, lines):
+        """Dispatcher: called from Connection when an event is received."""
+        for code, msg, data in lines:
+            evtype, args = self.decode1(msg)
+            self._map1.get(evtype, self.unknown_event)(evtype, *args)
+
+    def decode1(self, body):
+        """Unpack an event message into a type/arguments-tuple tuple."""
+        if " " in body:
+            evtype,body = body.split(" ",1)
+        else:
+            evtype,body = body,""
+        evtype = evtype.upper()
+        if evtype == "CIRC":
+            m = re.match(r"(\S+)\s+(\S+)(\s\S+)?", body)
+            if not m:
+                raise ProtocolError("CIRC event misformatted.")
+            status,ident,path = m.groups()
+            if path:
+                path = path.strip().split(",")
+            else:
+                path = []
+            args = status, ident, path
+        elif evtype == "STREAM":
+            m = re.match(r"(\S+)\s+(\S+)\s+(\S+)\s+(\S+)", body)
+            if not m:
+                raise ProtocolError("STREAM event misformatted.")
+            ident,status,circ,target = m.groups()
+            args = status, ident, target, circ
+        elif evtype == "ORCONN":
+            m = re.match(r"(\S+)\s+(\S+)", body)
+            if not m:
+                raise ProtocolError("ORCONN event misformatted.")
+            target, status = m.groups()
+            args = status, target
+        elif evtype == "BW":
+            m = re.match(r"(\d+)\s+(\d+)", body)
+            if not m:
+                raise ProtocolError("BANDWIDTH event misformatted.")
+            read, written = map(long, m.groups())
+            args = read, written
+        elif evtype in ("DEBUG", "INFO", "NOTICE", "WARN", "ERR"):
+            args = evtype, body
+        elif evtype == "NEWDESC":
+            args = ((" ".split(body)),)
+        elif evtype == "ADDRMAP":
+            m = re.match(r'(\S+)\s+(\S+)\s+(\"[^"]+\"|\w+)')
+            if not m:
+                raise ProtocolError("BANDWIDTH event misformatted.")
+            fromaddr, toaddr, when = m.groups()
+            if when.upper() == "NEVER":
+                when = None
+            else:
+                when = time.localtime(
+                    time.strptime(when[1:-1], "%Y-%m-%d %H:%M:%S"))
+            args = fromaddr, toaddr, when
+        else:
+            args = (body,)
+
+        return evtype, args
+
+    def unknown_event(self, eventtype, evtype, *args):
+        """Called when we get an event type we don't recognize.  This
+           is almost alwyas an error.
+        """
+        raise NotImplemented
+
+    def circ_status(self, eventtype, status, circID, path):
+        """Called when a circuit status changes if listening to CIRCSTATUS
+           events.  'status' is a member of CIRC_STATUS; circID is a numeric
+           circuit ID, and 'path' is the circuit's path so far as a list of
+           names.
+        """
+        raise NotImplemented
+
+    def stream_status(self, eventtype, status, streamID, target, circID="0"):
+        """Called when a stream status changes if listening to STREAMSTATUS
+           events.  'status' is a member of STREAM_STATUS; streamID is a
+           numeric stream ID, and 'target' is the destination of the stream.
+        """
+        raise NotImplemented
+
+    def or_conn_status(self, eventtype, status, target):
+        """Called when an OR connection's status changes if listening to
+           ORCONNSTATUS events. 'status' is a member of OR_CONN_STATUS; target
+           is the OR in question.
+        """
+        raise NotImplemented
+
+    def bandwidth(self, eventtype, read, written):
+        """Called once a second if listening to BANDWIDTH events.  'read' is
+           the number of bytes read; 'written' is the number of bytes written.
+        """
+        raise NotImplemented
+
+    def new_desc(self, eventtype, identities):
+        """Called when Tor learns a new server descriptor if listenting to
+           NEWDESC events.
+        """
+        raise NotImplemented
+
+    def msg(self, eventtype, severity, message):
+        """Called when a log message of a given severity arrives if listening
+           to INFO_MSG, NOTICE_MSG, WARN_MSG, or ERR_MSG events."""
+        raise NotImplemented
+
+    def address_mapped(self, eventtype, fromAddr, toAddr, expiry=None):
+        """Called when Tor adds a mapping for an address if listening
+           to ADDRESSMAPPED events.
+        """
+        raise NotImplemented
+
+class _ConnectionBase:
+    def __init__(self):
+        self._s = None
+        self._handler = None
+        self._handleFn = None
+        self._sendLock = threading.RLock()
+        self._queue = Queue.Queue()
+        self._thread = None
+        self._closedEx = None
+        self._closed = 0
+        self._closeHandler = None
+        self._eventThread = None
+        self._eventQueue = Queue.Queue()
+
+    def set_event_handler(self, handler):
+        """Cause future events from the Tor process to be sent to 'handler'.
+        """
+        raise NotImplemented
+
+    def set_close_handler(self, handler):
+        """Call 'handler' when the Tor process has closed its connection or
+           given us an exception.  If we close normally, no arguments are
+           provided; otherwise, it will be called with an exception as its
+           argument.
+        """
+        self._closeHandler = handler
+
+    def close(self):
+        """Shut down this controller connection"""
+        self._sendLock.acquire()
+        try:
+            self._queue.put("CLOSE")
+            self._eventQueue.put("CLOSE")
+            self._s.close()
+            self._s = None
+            self._closed = 1
+        finally:
+            self._sendLock.release()
+
+    def _read_reply(self):
+        """DOCDOC"""
+        raise NotImplementd
+
+    def launch_thread(self, daemon=1):
+        """Launch a background thread to handle messages from the Tor process."""
+        assert self._thread is None
+        t = threading.Thread(target=self._loop)
+        if daemon:
+            t.setDaemon(daemon)
+        t.start()
+        self._thread = t
+        t = threading.Thread(target=self._eventLoop)
+        if daemon:
+            t.setDaemon(daemon)
+        t.start()
+        self._eventThread = t
+        return self._thread
+
+    def _loop(self):
+        """Main subthread loop: Read commands from Tor, and handle them either
+           as events or as responses to other commands.
+        """
+        while 1:
+            ex = None
+            try:
+                isEvent, reply = self._read_reply()
+            except:
+                self._err(sys.exc_info())
+                return
+
+            if isEvent:
+                if self._handler is not None:
+                    self._eventQueue.put(reply)
+            else:
+                cb = self._queue.get()
+                cb(reply)
+
+    def _err(self, (tp, ex, tb), fromEventLoop=0):
+        """DOCDOC"""
+        if self._s:
+            try:
+                self.close()
+            except:
+                pass
+        self._sendLock.acquire()
+        try:
+            self._closedEx = ex
+            self._closed = 1
+        finally:
+            self._sendLock.release()
+        while 1:
+            try:
+                cb = self._queue.get(timeout=0)
+                if cb != "CLOSE":
+                    cb("EXCEPTION")
+            except Queue.Empty:
+                break
+        if self._closeHandler is not None:
+            self._closeHandler(ex)
+        return
+
+    def _eventLoop(self):
+        """DOCDOC"""
+        while 1:
+            reply = self._eventQueue.get()
+            if reply == "CLOSE":
+                return
+            try:
+                self._handleFn(reply)
+            except:
+                self._err(sys.exc_info(), 1)
+                return
+
+    def _sendImpl(self, sendFn, msg):
+        """DOCDOC"""
+        if self._thread is None:
+            self.launch_thread(1)
+        # This condition will get notified when we've got a result...
+        condition = threading.Condition()
+        # Here's where the result goes...
+        result = []
+
+        if self._closedEx is not None:
+            raise self._closedEx
+        elif self._closed:
+            raise TorCtl.TorCtlClosed()
+
+        def cb(reply,condition=condition,result=result):
+            condition.acquire()
+            try:
+                result.append(reply)
+                condition.notify()
+            finally:
+                condition.release()
+
+        # Sends a message to Tor...
+        self._sendLock.acquire()
+        try:
+            self._queue.put(cb)
+            sendFn(msg)
+        finally:
+            self._sendLock.release()
+
+        # Now wait till the answer is in...
+        condition.acquire()
+        try:
+            while not result:
+                condition.wait()
+        finally:
+            condition.release()
+
+        # ...And handle the answer appropriately.
+        assert len(result) == 1
+        reply = result[0]
+        if reply == "EXCEPTION":
+            raise self._closedEx
+
+        return reply
+
+class DebugEventHandler(EventHandler):
+    """Trivial event handler: dumps all events to stdout."""
+    def __init__(self, out=None):
+        if out is None:
+            out = sys.stdout
+        self._out = out
+
+    def handle0(self, body):
+        evtype, args = self.decode0(body)
+        print >>self._out,EVENT_TYPE.nameOf[evtype],args
+
+    def handle1(self, lines):
+        for code, msg, data in lines:
+            print >>self._out, msg
+
+def detectVersion(s):
+    """Helper: sends a trial command to Tor to tell whether it's running
+       the first or second version of the control protocol.
+    """
+    s.sendall("\x00\x00\r\n")
+    m = s.recv(4)
+    v0len, v0type = struct.unpack("!HH", m)
+    if v0type == '\x00\x00':
+        s.recv(v0len)
+        return 0
+    if '\n' not in m:
+        while 1:
+            c = s.recv(1)
+            if c == '\n':
+                break
+    return 1
+
+def parseHostAndPort(h):
+    """Given a string of the form 'address:port' or 'address' or
+       'port' or '', return a two-tuple of (address, port)
+    """
+    host, port = "localhost", 9100
+    if ":" in h:
+        i = h.index(":")
+        host = h[:i]
+        try:
+            port = int(h[i+1:])
+        except ValueError:
+            print "Bad hostname %r"%h
+            sys.exit(1)
+    elif h:
+        try:
+            port = int(h)
+        except ValueError:
+            host = h
+
+    return host, port
+
+def get_connection(sock):
+    """Given a socket attached to a Tor control port, detect the version of Tor
+       and return an appropriate 'Connection' object."""
+    v = detectVersion(sock)
+    if v == 0:
+        import TorCtl0
+        return TorCtl0.Connection(sock)
+    else:
+        import TorCtl1
+        return TorCtl1.Connection(sock)
+
+def secret_to_key(secret, s2k_specifier):
+    """Used to generate a hashed password string. DOCDOC."""
+    c = ord(s2k_specifier[8])
+    EXPBIAS = 6
+    count = (16+(c&15)) << ((c>>4) + EXPBIAS)
+
+    d = sha.new()
+    tmp = s2k_specifier[:8]+secret
+    slen = len(tmp)
+    while count:
+        if count > slen:
+            d.update(tmp)
+            count -= slen
+        else:
+            d.update(tmp[:count])
+            count = 0
+    return d.digest()
+
+def urandom_rng(n):
+    """Try to read some entropy from the platform entropy source."""
+    f = open('/dev/urandom', 'rb')
+    try:
+        return f.read(n)
+    finally:
+        f.close()
+
+def s2k_gen(secret, rng=None):
+    """DOCDOC"""
+    if rng is None:
+        if hasattr(os, "urandom"):
+            rng = os.urandom
+        else:
+            rng = urandom_rng
+    spec = "%s%s"%(rng(8), chr(96))
+    return "16:%s"%(
+        binascii.b2a_hex(spec + secret_to_key(secret, spec)))
+
+def s2k_check(secret, k):
+    """DOCDOC"""
+    assert k[:3] == "16:"
+
+    k =  binascii.a2b_hex(k[3:])
+    return secret_to_key(secret, k[:9]) == k[9:]
+
+def run_example(host,port):
+    print "host is %s:%d"%(host,port)
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.connect((host,port))
+    c = Connection(s)
+    c.set_event_handler(DebugEventHandler())
+    th = c.launchThread()
+    c.authenticate()
+    print "nick",`c.get_option("nickname")`
+    print c.get_option("DirFetchPeriod\n")
+    print `c.get_info("version")`
+    #print `c.get_info("desc/name/moria1")`
+    print `c.get_info("network-status")`
+    print `c.get_info("addr-mappings/all")`
+    print `c.get_info("addr-mappings/config")`
+    print `c.get_info("addr-mappings/cache")`
+    print `c.get_info("addr-mappings/control")`
+    print `c.map_address([("0.0.0.0", "Foobar.com"),
+                        ("1.2.3.4", "foobaz.com"),
+                        ("frebnitz.com", "5.6.7.8"),
+                        (".", "abacinator.onion")])`
+    print `c.extend_circuit(0,["moria1"])`
+    try:
+        print `c.extend_circuit(0,[""])`
+    except ErrorReply:
+        print "got error. good."
+    #send_signal(s,1)
+    #save_conf(s)
+
+    #set_option(s,"1")
+    #set_option(s,"bandwidthburstbytes 100000")
+    #set_option(s,"runasdaemon 1")
+    #set_events(s,[EVENT_TYPE.WARN])
+    c.set_events([EVENT_TYPE.ORCONNSTATUS, EVENT_TYPE.STREAMSTATUS,
+                  EVENT_TYPE.CIRCSTATUS, EVENT_TYPE.INFO_MSG,
+                  EVENT_TYPE.BANDWIDTH])
+
+    th.join()
+    return
+
+if __name__ == '__main__':
+    if len(sys.argv) > 2:
+        print "Syntax: TorControl.py torhost:torport"
+        sys.exit(0)
+    else:
+        sys.argv.append("localhost:9051")
+    sh,sp = parseHostAndPort(sys.argv[1])
+    run_example(sh,sp)
+


Property changes on: blossom/trunk/TorCtl.py
___________________________________________________________________
Name: svn:executable
   + *

Added: blossom/trunk/TorCtl0.py
===================================================================
--- blossom/trunk/TorCtl0.py	                        (rev 0)
+++ blossom/trunk/TorCtl0.py	2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,446 @@
+#!/usr/bin/python
+# TorCtl.py -- Python module to interface with Tor Control interface.
+# Copyright 2005 Nick Mathewson -- See LICENSE for licensing information.
+#$Id: TorCtl0.py,v 1.7 2005/11/19 19:42:31 nickm Exp $
+
+"""
+TorCtl0 -- Library to control Tor processes.  See TorCtlDemo.py for example use.
+"""
+
+import binascii
+import os
+import sha
+import socket
+import struct
+import sys
+import TorCtl
+
+__all__ = [
+    "MSG_TYPE", "EVENT_TYPE", "CIRC_STATUS", "STREAM_STATUS",
+    "OR_CONN_STATUS", "SIGNAL", "ERR_CODES",
+    "TorCtlError", "ProtocolError", "ErrorReply", "Connection", "EventHandler",
+    ]
+
+class _Enum:
+    # Helper: define an ordered dense name-to-number 1-1 mapping.
+    def __init__(self, start, names):
+        self.nameOf = {}
+        idx = start
+        for name in names:
+            setattr(self,name,idx)
+            self.nameOf[idx] = name
+            idx += 1
+
+class _Enum2:
+    # Helper: define an ordered sparse name-to-number 1-1 mapping.
+    def __init__(self, **args):
+        self.__dict__.update(args)
+        self.nameOf = {}
+        for k,v in args.items():
+            self.nameOf[v] = k
+
+# Message types that client or server can send.
+MSG_TYPE = _Enum(0x0000,
+                 ["ERROR",
+                  "DONE",
+                  "SETCONF",
+                  "GETCONF",
+                  "CONFVALUE",
+                  "SETEVENTS",
+                  "EVENT",
+                  "AUTH",
+                  "SAVECONF",
+                  "SIGNAL",
+                  "MAPADDRESS",
+                  "GETINFO",
+                  "INFOVALUE",
+                  "EXTENDCIRCUIT",
+                  "ATTACHSTREAM",
+                  "POSTDESCRIPTOR",
+                  "FRAGMENTHEADER",
+                  "FRAGMENT",
+                  "REDIRECTSTREAM",
+                  "CLOSESTREAM",
+                  "CLOSECIRCUIT",
+                  ])
+
+# Make sure that the enumeration code is working.
+assert MSG_TYPE.SAVECONF == 0x0008
+assert MSG_TYPE.CLOSECIRCUIT == 0x0014
+
+# Types of "EVENT" message.
+EVENT_TYPE = _Enum(0x0001,
+                   ["CIRCSTATUS",
+                    "STREAMSTATUS",
+                    "ORCONNSTATUS",
+                    "BANDWIDTH",
+                    "OBSOLETE_LOG",
+                    "NEWDESC",
+                    "DEBUG_MSG",
+                    "INFO_MSG",
+                    "NOTICE_MSG",
+                    "WARN_MSG",
+                    "ERR_MSG",
+                    ])
+
+assert EVENT_TYPE.ERR_MSG == 0x000B
+assert EVENT_TYPE.OBSOLETE_LOG == 0x0005
+
+# Status codes for "CIRCSTATUS" events.
+CIRC_STATUS = _Enum(0x00,
+                    ["LAUNCHED",
+                     "BUILT",
+                     "EXTENDED",
+                     "FAILED",
+                     "CLOSED"])
+
+# Status codes for "STREAMSTATUS" events
+STREAM_STATUS = _Enum(0x00,
+                      ["SENT_CONNECT",
+                       "SENT_RESOLVE",
+                       "SUCCEEDED",
+                       "FAILED",
+                       "CLOSED",
+                       "NEW_CONNECT",
+                       "NEW_RESOLVE",
+                       "DETACHED"])
+
+# Status codes for "ORCONNSTATUS" events
+OR_CONN_STATUS = _Enum(0x00,
+                       ["LAUNCHED","CONNECTED","FAILED","CLOSED"])
+
+# Signal codes for "SIGNAL" events.
+SIGNAL = _Enum2(HUP=0x01,INT=0x02,USR1=0x0A,USR2=0x0C,TERM=0x0F)
+
+# Error codes for "ERROR" events.
+ERR_CODES = {
+  0x0000 : "Unspecified error",
+  0x0001 : "Internal error",
+  0x0002 : "Unrecognized message type",
+  0x0003 : "Syntax error",
+  0x0004 : "Unrecognized configuration key",
+  0x0005 : "Invalid configuration value",
+  0x0006 : "Unrecognized byte code",
+  0x0007 : "Unauthorized",
+  0x0008 : "Failed authentication attempt",
+  0x0009 : "Resource exhausted",
+  0x000A : "No such stream",
+  0x000B : "No such circuit",
+  0x000C : "No such OR"
+}
+
+def _unpack_singleton_msg(msg):
+    """Helper: unpack a single packet.  Return (None, minLength, body-so-far)
+       on incomplete packet or (type,body,rest) on somplete packet
+    """
+    if len(msg) < 4:
+        return None, 4, msg
+    length,type = struct.unpack("!HH",msg)
+    if len(msg) >= 4+length:
+        return type,msg[4:4+length],msg[4+length:]
+    else:
+        return None,4+length,msg
+
+def _minLengthToPack(bytes):
+    """Return the minimum number of bytes needed to pack the message 'smg'"""
+    whole,left = divmod(bytes,65535)
+    if left:
+        return whole*(65535+4)+4+left
+    else:
+        return whole*(65535+4)
+
+def _unpack_msg(msg):
+    "returns as for _unpack_singleton_msg"
+    tp,body,rest = _unpack_singleton_msg(msg)
+    if tp != MSG_TYPE.FRAGMENTHEADER:
+        return tp, body, rest
+
+    if len(body) < 6:
+        raise ProtocolError("FRAGMENTHEADER message too short")
+
+    realType,realLength = struct.unpack("!HL", body[:6])
+
+    # Okay; could the message _possibly_ be here?
+    minLength = _minLengthToPack(realLength+6)
+    if len(msg) < minLength:
+        return None,  minLength, msg
+
+    # Okay; optimistically try to build up the msg.
+    soFar = [ body[6:] ]
+    lenSoFarLen = len(body)-6
+    while len(rest)>=4 and lenSoFar < realLength:
+        ln, tp = struct.unpack("!HH", rest[:4])
+        if tp != MSG_TYPE.FRAGMENT:
+            raise ProtocolError("Missing FRAGMENT message")
+        soFar.append(rest[4:4+ln])
+        lenSoFar += ln
+        if 4+ln > len(rest):
+            rest = ""
+            leftInPacket = 4+ln-len(rest)
+        else:
+            rest = rest[4+ln:]
+            leftInPacket=0
+
+    if lenSoFar == realLength:
+        return realType, "".join(soFar), rest
+    elif lenSoFar > realLength:
+        raise ProtocolError("Bad fragmentation: message longer than declared")
+    else:
+        inOtherPackets = realLength-lenSoFar-leftInPacket
+        minLength = _minLengthToPack(inOtherPackets)
+        return None, len(msg)+leftInPacket+inOtherPackets, msg
+
+def _receive_singleton_msg(s):
+    """Read a single packet from the socket s.
+    """
+    body = ""
+    header = s.recv(4)
+    if not header:
+        raise TorCtl.TorCtlClosed()
+    length,type = struct.unpack("!HH",header)
+    if length:
+        while length > len(body):
+            more = s.recv(length-len(body))
+            if not more:
+                raise TorCtl.TorCtlClosed()
+            body += more
+    return length,type,body
+
+def _receive_message(s):
+    """Read a single message (possibly multi-packet) from the socket s."""
+    length, tp, body = _receive_singleton_msg(s)
+    if tp != MSG_TYPE.FRAGMENTHEADER:
+        return length, tp, body
+    if length < 6:
+        raise ProtocolError("FRAGMENTHEADER message too short")
+    realType,realLength = struct.unpack("!HL", body[:6])
+    data = [ body[6:] ]
+    soFar = len(data[0])
+    while 1:
+        length, tp, body = _receive_singleton_msg(s)
+        if tp != MSG_TYPE.FRAGMENT:
+            raise ProtocolError("Missing FRAGMENT message")
+        soFar += length
+        data.append(body)
+        if soFar == realLength:
+            return realLength, realType, "".join(data)
+        elif soFar > realLength:
+            raise ProtocolError("FRAGMENT message too long!")
+
+def pack_message(type, body=""):
+    """Given a message type and optional message body, generate a set of
+       packets to send.
+    """
+    length = len(body)
+    if length < 65536:
+        reqheader = struct.pack("!HH", length, type)
+        return "%s%s"%(reqheader,body)
+
+    fragheader = struct.pack("!HHHL",
+                             65535, MSG_TYPE.FRAGMENTHEADER, type, length)
+    msgs = [ fragheader, body[:65535-6] ]
+    body = body[65535-6:]
+    while body:
+        if len(body) > 65535:
+            fl = 65535
+        else:
+            fl = len(body)
+        fragheader = struct.pack("!HH", MSG_TYPE.FRAGMENT, fl)
+        msgs.append(fragheader)
+        msgs.append(body[:fl])
+        body = body[fl:]
+
+    return "".join(msgs)
+
+def _parseKV(body,sep=" ",term="\n"):
+    """Helper: parse a key/value list of the form [key sep value term]* .
+       Return a list of (k,v)."""
+    res = []
+    for line in body.split(term):
+        if not line: continue
+        k, v = line.split(sep,1)
+        res.append((k,v))
+    return res
+
+def _unterminate(s):
+    """Strip trailing NUL characters from s."""
+    if s[-1] == '\0':
+        return s[:-1]
+    else:
+        return s
+
+class Connection(TorCtl._ConnectionBase):
+    """A Connection represents a connection to the Tor process."""
+    def __init__(self, sock):
+        """Create a Connection to communicate with the Tor process over the
+           socket 'sock'.
+        """
+        TorCtl._ConnectionBase.__init__(self)
+        self._s = sock
+
+    def set_event_handler(self, handler):
+        """Cause future events from the Tor process to be sent to 'handler'.
+        """
+        self._handler = handler
+        self._handleFn = handler.handle0
+
+    def _doSend(self, (type, body)):
+        """Helper: Deliver a command of type 'type' and body 'body' to Tor.
+        """
+        self._s.sendall(pack_message(type, body))
+
+    def _read_reply(self):
+        length, tp, body = _receive_message(self._s)
+        return (tp == MSG_TYPE.EVENT), (tp, body)
+
+    def _sendAndRecv(self, tp, msg="", expectedTypes=(MSG_TYPE.DONE,)):
+        """Helper: Send a command of type 'tp' and body 'msg' to Tor,
+           and wait for a command in response.  If the response type is
+           in expectedTypes, return a (tp,body) tuple.  If it is an error,
+           raise ErrorReply.  Otherwise, raise ProtocolError.
+        """
+
+        tp, msg = self.sendImpl(self._doSend, (tp, msg))
+        if tp in expectedTypes:
+            return tp, msg
+        elif tp == MSG_TYPE.ERROR:
+            if len(msg)<2:
+                raise ProtocolError("(Truncated error message)")
+            errCode, = struct.unpack("!H", msg[:2])
+            raise ErrorReply((errCode,
+                              ERR_CODES.get(errCode,"[unrecognized]"),
+                              msg[2:]))
+        else:
+            raise ProtocolError("Unexpectd message type 0x%04x"%tp)
+
+    def authenticate(self, secret=""):
+        """Send an authenticating secret to Tor.  You'll need to call
+           this method before other commands.  You need to use a
+           password if Tor expects one.
+        """
+        self._sendAndRecv(MSG_TYPE.AUTH,secret)
+
+    def get_option(self,name):
+        """Get the value of the configuration option named 'name'.  To
+           retrieve multiple values, pass a list for 'name' instead of
+           a string.  Returns a list of (key,value) pairs.
+        """
+        if not isinstance(name, str):
+            name = "".join(["%s\n"%s for s in name])
+        tp,body = self._sendAndRecv(MSG_TYPE.GETCONF,name,[MSG_TYPE.CONFVALUE])
+        return _parseKV(body)
+
+    def set_option(self,key,value):
+        """Set the value of the configuration option 'key' to the
+           value 'value'.
+        """
+        self.set_options([key, value])
+
+    def set_options(self,kvlist):
+        """Given a list of (key,value) pairs, set them as configuration
+           options.
+        """
+        msg = "".join(["%s %s\n" for k,v in kvlist])
+        self._sendAndRecv(MSG_TYPE.SETCONF,msg)
+
+    def reset_options(self, keylist):
+        msg = "".join(["%s\n" for k in keylist])
+        self._sendAndRecv(MSG_TYPE.SETCONF,msg)
+
+    def get_info(self,name):
+        """Return the value of the internal information field named
+           'name'.  To retrieve multiple values, pass a list for
+           'name' instead of a string.  Returns a dictionary of
+           key->value mappings.
+        """
+        if not isinstance(name, str):
+            name = "".join(["%s\n"%s for s in name])
+        tp, body = self._sendAndRecv(MSG_TYPE.GETINFO,name,[MSG_TYPE.INFOVALUE])
+        kvs = body.split("\0")
+        d = {}
+        for i in xrange(0,len(kvs)-1,2):
+            d[kvs[i]] = kvs[i+1]
+        return d
+
+    def set_events(self,events):
+        """Change the list of events that the event handler is interested
+           in to those in 'events', which is a list of EVENT_TYPE members
+           or their corresponding strings.
+        """
+        evs = []
+        for ev in events:
+            if isinstance(ev, types.StringType):
+                evs.append(getattr(EVENT_TYPE, ev.upper()))
+            else:
+                evs.append(ev)
+        self._sendAndRecv(MSG_TYPE.SETEVENTS,
+                     "".join([struct.pack("!H", event) for event in events]))
+
+    def save_conf(self):
+        """Flush all configuration changes to disk.
+        """
+        self._sendAndRecv(MSG_TYPE.SAVECONF)
+
+    def send_signal(self, sig):
+        """Send the signal 'sig' to the Tor process; 'sig' must be a member of
+           SIGNAL or a corresponding string.
+        """
+        try:
+            sig = sig.upper()
+        except AttributeError:
+            pass
+        sig = { "HUP" : 0x01, "RELOAD" : 0x01,
+                "INT" : 0x02, "SHUTDOWN" : 0x02,
+                "DUMP" : 0x0A, "USR1" : 0x0A,
+                "USR2" : 0x0C, "DEBUG" : 0x0C,
+                "TERM" : 0x0F, "HALT" : 0x0F
+                }.get(sig,sig)
+        self._sendAndRecv(MSG_TYPE.SIGNAL,struct.pack("B",sig))
+
+    def map_address(self, kvList):
+        """Given a list of (old-address,new-address), have Tor redirect
+           streams from old-address to new-address.  Old-address can be in a
+           special "dont-care" form of "0.0.0.0" or ".".
+        """
+        msg = [ "%s %s\n"%(k,v) for k,v in kvList ]
+        tp, body = self._sendAndRecv(MSG_TYPE.MAPADDRESS,"".join(msg))
+        return _parseKV(body)
+
+    def extend_circuit(self, circid, hops):
+        """Tell Tor to extend the circuit identified by 'circid' through the
+           servers named in the list "hops".
+        """
+        msg = struct.pack("!L",long(circid)) + ",".join(hops) + "\0"
+        tp, body = self._sendAndRecv(MSG_TYPE.EXTENDCIRCUIT,msg)
+        if len(body) != 4:
+            raise ProtocolError("Extendcircuit reply too short or long")
+        return struct.unpack("!L",body)[0]
+
+    def redirect_stream(self, streamid, newtarget):
+        """Tell Tor to change the target address of the stream identified by
+           'streamid' from its old value to 'newtarget'."""
+        msg = struct.pack("!L",long(streamid)) + newtarget + "\0"
+        self._sendAndRecv(MSG_TYPE.REDIRECTSTREAM,msg)
+
+    def attach_stream(self, streamid, circid):
+        """Tell Tor To attach stream 'streamid' to circuit 'circid'."""
+        msg = struct.pack("!LL",long(streamid), long(circid))
+        self._sendAndRecv(MSG_TYPE.ATTACHSTREAM,msg)
+
+    def close_stream(self, streamid, reason=0, flags=()):
+        """Close the stream 'streamid'. """
+        msg = struct.pack("!LBB",long(streamid),reason,flags)
+        self._sendAndRecv(MSG_TYPE.CLOSESTREAM,msg)
+
+    def close_circuit(self, circid, flags=()):
+        """Close the circuit 'circid'."""
+        if "IFUNUSED" in flags:
+            flags=1
+        else:
+            flags=0
+        msg = struct.pack("!LB",long(circid),flags)
+        self._sendAndRecv(MSG_TYPE.CLOSECIRCUIT,msg)
+
+    def post_descriptor(self, descriptor):
+        """Tell Tor about a new descriptor in 'descriptor'."""
+        self._sendAndRecv(MSG_TYPE.POSTDESCRIPTOR,descriptor)

Added: blossom/trunk/TorCtl1.py
===================================================================
--- blossom/trunk/TorCtl1.py	                        (rev 0)
+++ blossom/trunk/TorCtl1.py	2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,338 @@
+#!/usr/bin/python
+# TorCtl.py -- Python module to interface with Tor Control interface.
+# Copyright 2005 Nick Mathewson -- See LICENSE for licensing information.
+#$Id: TorCtl1.py,v 1.26 2006/01/10 04:05:31 goodell Exp $
+
+import binascii
+import os
+import re
+import socket
+import sys
+import types
+import TorCtl
+
+def _quote(s):
+    return re.sub(r'([\r\n\\\"])', r'\\\1', s)
+
+def _escape_dots(s, translate_nl=1):
+    if translate_nl:
+        lines = re.split(r"\r?\n", s)
+    else:
+        lines = s.split("\r\n")
+    if lines and not lines[-1]:
+        del lines[-1]
+    for i in xrange(len(lines)):
+        if lines[i].startswith("."):
+            lines[i] = "."+lines[i]
+    lines.append(".\r\n")
+    return "\r\n".join(lines)
+
+def _unescape_dots(s, translate_nl=1):
+    lines = s.split("\r\n")
+
+    for i in xrange(len(lines)):
+        if lines[i].startswith("."):
+            lines[i] = lines[i][1:]
+
+    if lines and lines[-1]:
+        lines.append("")
+
+    if translate_nl:
+        return "\n".join(lines)
+    else:
+        return "\r\n".join(lines)
+
+class _BufSock:
+    def __init__(self, s):
+        self._s = s
+        self._buf = []
+
+    def readline(self):
+        if self._buf:
+            idx = self._buf[0].find('\n')
+            if idx >= 0:
+                result = self._buf[0][:idx+1]
+                self._buf[0] = self._buf[0][idx+1:]
+                return result
+
+        while 1:
+            s = self._s.recv(128)
+            if not s:
+                raise TorCtl.TorCtlClosed()
+            idx = s.find('\n')
+            if idx >= 0:
+                self._buf.append(s[:idx+1])
+                result = "".join(self._buf)
+                rest = s[idx+1:]
+                if rest:
+                    self._buf = [ rest ]
+                else:
+                    del self._buf[:]
+                return result
+            else:
+                self._buf.append(s)
+
+    def write(self, s):
+        self._s.send(s)
+
+    def close(self):
+        self._s.close()
+
+def _read_reply(f,debugFile=None):
+    lines = []
+    while 1:
+        line = f.readline().strip()
+        if debugFile:
+            debugFile.write("    %s\n" % line)
+        if len(line)<4:
+            raise TorCtl.ProtocolError("Badly formatted reply line: Too short")
+        code = line[:3]
+        tp = line[3]
+        s = line[4:]
+        if tp == "-":
+            lines.append((code, s, None))
+        elif tp == " ":
+            lines.append((code, s, None))
+            return lines
+        elif tp != "+":
+            raise TorCtl.ProtocolError("Badly formatted reply line: unknown type %r"%tp)
+        else:
+            more = []
+            while 1:
+                line = f.readline()
+                if debugFile and tp != "+":
+                    debugFile.write("    %s" % line)
+                if line in (".\r\n", ".\n"):
+                    break
+                more.append(line)
+            lines.append((code, s, _unescape_dots("".join(more))))
+
+class Connection(TorCtl._ConnectionBase):
+    """A Connection represents a connection to the Tor process."""
+    def __init__(self, sock):
+        """Create a Connection to communicate with the Tor process over the
+           socket 'sock'.
+        """
+        TorCtl._ConnectionBase.__init__(self)
+        self._s = _BufSock(sock)
+        self._debugFile = None
+
+    def debug(self, f):
+        """DOCDOC"""
+        self._debugFile = f
+
+    def set_event_handler(self, handler):
+        """Cause future events from the Tor process to be sent to 'handler'.
+        """
+        self._handler = handler
+        self._handleFn = handler.handle1
+
+    def _read_reply(self):
+        lines = _read_reply(self._s, self._debugFile)
+        isEvent = (lines and lines[0][0][0] == '6')
+        return isEvent, lines
+
+    def _doSend(self, msg):
+        if self._debugFile:
+            amsg = msg
+            lines = amsg.split("\n")
+            if len(lines) > 2:
+                amsg = "\n".join(lines[:2]) + "\n"
+            self._debugFile.write(">>> %s" % amsg)
+        self._s.write(msg)
+
+    def _sendAndRecv(self, msg="", expectedTypes=("250", "251")):
+        """Helper: Send a command 'msg' to Tor, and wait for a command
+           in response.  If the response type is in expectedTypes,
+           return a list of (tp,body,extra) tuples.  If it is an
+           error, raise ErrorReply.  Otherwise, raise TorCtl.ProtocolError.
+        """
+        if type(msg) == types.ListType:
+            msg = "".join(msg)
+        assert msg.endswith("\r\n")
+
+        lines = self._sendImpl(self._doSend, msg)
+        # print lines
+        for tp, msg, _ in lines:
+            if tp[0] in '45':
+                raise TorCtl.ErrorReply("%s %s"%(tp, msg))
+            if tp not in expectedTypes:
+                raise TorCtl.ProtocolError("Unexpectd message type %r"%tp)
+
+        return lines
+
+    def authenticate(self, secret=""):
+        """Send an authenticating secret to Tor.  You'll need to call this
+           method before Tor can start.
+        """
+        hexstr = binascii.b2a_hex(secret)
+        self._sendAndRecv("AUTHENTICATE %s\r\n"%hexstr)
+
+    def get_option(self, name):
+        """Get the value of the configuration option named 'name'.  To
+           retrieve multiple values, pass a list for 'name' instead of
+           a string.  Returns a list of (key,value) pairs.
+           Refer to section 3.3 of control-spec.txt for a list of valid names.
+        """
+        if not isinstance(name, str):
+            name = " ".join(name)
+        lines = self._sendAndRecv("GETCONF %s\r\n" % name)
+
+        r = []
+        for _,line,_ in lines:
+            try:
+                key, val = line.split("=", 1)
+                r.append((key,val))
+            except ValueError:
+                r.append((line, None))
+
+        return r
+
+    def set_option(self, key, value):
+        """Set the value of the configuration option 'key' to the value 'value'.
+        """
+        self.set_options([(key, value)])
+
+    def set_options(self, kvlist):
+        """Given a list of (key,value) pairs, set them as configuration
+           options.
+        """
+        if not kvlist:
+            return
+        msg = " ".join(["%s=%s"%(k,_quote(v)) for k,v in kvlist])
+        self._sendAndRecv("SETCONF %s\r\n"%msg)
+
+    def reset_options(self, keylist):
+        """Reset the options listed in 'keylist' to their default values.
+
+           Tor started implementing this command in version 0.1.1.7-alpha;
+           previous versions wanted you to set configuration keys to "".
+           That no longer works.
+        """
+        self._sendAndRecv("RESETCONF %s\r\n"%(" ".join(keylist)))
+
+    def get_info(self, name):
+        """Return the value of the internal information field named 'name'.
+           Refer to section 3.9 of control-spec.txt for a list of valid names.
+           DOCDOC
+        """
+        if not isinstance(name, str):
+            name = " ".join(name)
+        lines = self._sendAndRecv("GETINFO %s\r\n"%name)
+        d = {}
+        for _,msg,more in lines:
+            if msg == "OK":
+                break
+            try:
+                k,rest = msg.split("=",1)
+            except ValueError:
+                raise TorCtl.ProtocolError("Bad info line %r",msg)
+            if more:
+                d[k] = more
+            else:
+                d[k] = rest
+        return d
+
+    def set_events(self, events):
+        """Change the list of events that the event handler is interested
+           in to those in 'events', which is a list of event names.
+           Recognized event names are listed in section 3.3 of the control-spec
+        """
+        evs = []
+
+        # Translate options supported by old interface.
+        for e in events:
+            if e == 0x0001 or e == "CIRCSTATUS":
+                e = "CIRC"
+            elif e == 0x0002 or e == "STREAMSTATUS":
+                e = "STREAM"
+            elif e == 0x0003 or e == "ORCONNSTATUS":
+                e = "ORCONN"
+            elif e == 0x0004 or e == "BANDWIDTH":
+                e = "BW"
+            elif e == 0x0005 or e == "OBSOLETE_LOG":
+                coneinue
+            elif e == 0x0006 or e == "NEWDESC":
+                e = "NEWDESC"
+            elif e == 0x0007 or e == "DEBUG_MSG":
+                continue
+            elif e == 0x0008 or e == "INFO_MSG":
+                e = "INFO"
+            elif e == 0x0008 or e == "NOTICE_MSG":
+                e = "NOTICE"
+            elif e == 0x0008 or e == "WARN_MSG":
+                e = "WARN"
+            elif e == 0x0008 or e == "ERR_MSG":
+                e = "ERR"
+            evs.append(e)
+
+        self._sendAndRecv("SETEVENTS %s\r\n" % " ".join(evs))
+
+    def save_conf(self):
+        """Flush all configuration changes to disk.
+        """
+        self._sendAndRecv("SAVECONF\r\n")
+
+    def send_signal(self, sig):
+        """Send the signal 'sig' to the Tor process; The allowed values for
+           'sig' are listed in section 3.6 of control-spec.
+        """
+        sig = { 0x01 : "HUP",
+                0x02 : "INT",
+                0x0A : "USR1",
+                0x0C : "USR2",
+                0x0F : "TERM" }.get(sig,sig)
+        self._sendAndRecv("SIGNAL %s\r\n"%sig)
+
+    def map_address(self, kvList):
+        if not kvList:
+            return
+        m = " ".join([ "%s=%s" for k,v in kvList])
+        lines = self._sendAndRecv("MAPADDRESS %s\r\n"%m)
+        r = []
+        for _,line,_ in lines:
+            try:
+                key, val = line.split("=", 1)
+            except ValueError:
+                raise TorCtl.ProtocolError("Bad address line %r",v)
+            r.append((key,val))
+        return r
+
+    def extend_circuit(self, circid, hops):
+        """Tell Tor to extend the circuit identified by 'circid' through the
+           servers named in the list 'hops'.
+        """
+        if circid is None:
+            circid = "0"
+        lines = self._sendAndRecv("EXTENDCIRCUIT %s %s\r\n"
+                                  %(circid, ",".join(hops)))
+        tp,msg,_ = lines[0]
+        m = re.match(r'EXTENDED (\S*)', msg)
+        if not m:
+            raise TorCtl.ProtocolError("Bad extended line %r",msg)
+        return m.group(1)
+
+    def redirect_stream(self, streamid, newaddr, newport=""):
+        """DOCDOC"""
+        if newport:
+            self._sendAndRecv("REDIRECTSTREAM %s %s %s\r\n"%(streamid, newaddr, newport))
+        else:
+            self._sendAndRecv("REDIRECTSTREAM %s %s\r\n"%(streamid, newaddr))
+
+    def attach_stream(self, streamid, circid):
+        """DOCDOC"""
+        self._sendAndRecv("ATTACHSTREAM %s %s\r\n"%(streamid, circid))
+
+    def close_stream(self, streamid, reason=0, flags=()):
+        """DOCDOC"""
+        self._sendAndRecv("CLOSESTREAM %s %s %s\r\n"
+                          %(streamid, reason, "".join(flags)))
+
+    def close_circuit(self, circid, reason=0, flags=()):
+        """DOCDOC"""
+        self._sendAndRecv("CLOSECIRCUIT %s %s %s\r\n"
+                          %(circid, reason, "".join(flags)))
+
+    def post_descriptor(self, desc):
+        self._sendAndRecv("+POSTDESCRIPTOR\r\n%s"%_escape_dots(desc))
+

Added: blossom/trunk/blossom.pl
===================================================================
--- blossom/trunk/blossom.pl	                        (rev 0)
+++ blossom/trunk/blossom.pl	2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,442 @@
+#!/usr/bin/perl -w
+# $Id: blossom.pl,v 1.19 2006-03-20 23:19:33 goodell Exp $
+$license = <<EOF
+Copyright (c) 2005 Geoffrey Goodell.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of version 2 of the GNU General Public License as published by the
+Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+Place - Suite 330, Boston, MA  02111-1307, USA.
+
+EOF
+;
+
+use strict;
+use Socket;
+
+# global configuration parameters
+
+my $CACHE           = "/var/cache/www-data";
+my $F_CCODES        = "/afs/eecs.harvard.edu/user/goodell/misc/country-codes.txt";
+my $URL_FLAGS       = "http://afs.eecs.harvard.edu/~goodell/flags";;
+my $URL_ICONS       = "http://afs.eecs.harvard.edu/~goodell/icons";;
+my $URL_EXIT        = "http://serifos.eecs.harvard.edu/cgi-bin/exit.pl";;
+my $URL_PROXY       = "http://serifos.eecs.harvard.edu/proxy/";;
+my $URL_SELF        = "http://serifos.eecs.harvard.edu/cgi-bin/blossom.pl";;
+my $URL_SOURCE      = "http://afs.eecs.harvard.edu/~goodell/blossom/src/blossom.pl";;
+my $URL_HOME        = "http://afs.eecs.harvard.edu/~goodell/blossom/";;
+my $WHOIS_SCRIPT    = "/cgi-bin/whois.pl";
+my $STATUS          = "?ports=80&addr=1&textonly=1";
+my $BLOSSOM         = "$STATUS&blossom=lefkada:9031";
+my $BLOSSOM_TAG     = "U0";
+my $BLOSSOM_TEXT    = "Blossom";
+my $TITLE           = "Blossom User Interface";
+my $WGET            = "/usr/bin/wget -O -";
+my $ICON_V1         = "v1.gif";
+my $ICON_V2         = "v2.gif";
+my $ICON_V3         = "v3.gif";
+my $F_SIZE          = "width=18 height=12";
+my $V1_MINBW        = 10;
+my $V2_MINBW        = 60;
+my $V3_MINBW        = 400;
+
+my %ccode           = ();
+my %nodes           = ();
+my %uri_fields      = ();
+
+my $cachefile       = "blossom.html";
+my $response        = "";
+my $uri             = "";
+my $method          = undef;
+
+use vars qw($license);
+
+sub addrouters($) {
+    my $href        = shift;
+    my @sorted      = undef;
+    my $response    = "";
+    my %routers     = %$href;
+
+    @sorted = sort keys %routers;
+
+    foreach my $router (@sorted) {
+        $response .= "<tr>\n    " . $routers{$router} . "</tr>\n";
+    }
+    return $response;
+}
+
+sub parsewhois($$$) {
+    my ($tag, $default, $arrayref) = (shift, shift, shift);
+    my $t;
+    my @lines = @$arrayref;
+    my @matches = grep /^$tag/i, @lines;
+
+    chomp $matches[$#matches] if $matches[$#matches];
+    ($t = $matches[$#matches] || $default) =~ s/\S+\s+//;
+    return $t;
+}
+
+sub modify_url($$) {
+    my ($modurl, $suffix) = (shift, shift);
+
+    unless($modurl eq "") {
+        $modurl =~ s/%3A/:/g;
+        $modurl =~ s/%2F/\//g;
+
+        $modurl = "http://$modurl"; if $modurl !~ /^http:\/\//;
+        $modurl = "$modurl/" if $modurl !~ /^http:\/\/.*\//;
+
+        if($modurl =~ /^(http:\/\/[A-Za-z0-9-.]+)([\/:].*)$/) {
+            $modurl = "$1.$suffix$2";
+        }
+
+        $modurl = "$URL_PROXY$modurl" if $uri_fields{"proxy"};
+    }
+    return $modurl;
+}
+
+sub report_router($$$$$$) {
+    my ($router, $address, $bandwidth, $netname, $modurl, $b)
+            = (shift, shift, shift, shift, shift, shift);
+
+    my $ab          = "";
+    my $bb          = "";
+    my $cb          = "";
+    my $icon        = "";
+    my $r           = "";
+
+    if($b) {
+        $ab = " class=\"unverified\"";
+        $bb = " class=\"unverified\"";
+        $cb = "*";
+    } else {
+        $ab = " class=\"standard\"";
+        $bb = "";
+        $cb = "";
+    }
+
+    my $modlink     = "<a$ab href=\"$modurl\">$cb$router</a>";
+
+    # security feature
+    $address = "" if $address !~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/;
+
+    if($bandwidth >= $V3_MINBW*1000) {
+        $icon = "<img $F_SIZE src=\"$URL_ICONS/$ICON_V3\" alt=\"v3\">";
+    } elsif($bandwidth >= $V2_MINBW*1000) {
+        $icon = "<img $F_SIZE src=\"$URL_ICONS/$ICON_V2\" alt=\"v2\">";
+    } else {
+        $icon = "<img $F_SIZE src=\"$URL_ICONS/$ICON_V1\" alt=\"v1\">";
+    }
+    $icon = "<acronym title=\"$bandwidth B/s\">$icon</acronym>";
+
+    $bandwidth = sprintf "%4s kB/s", int($bandwidth/1000);
+    $bandwidth =~ s/ /&nbsp;/g;
+
+    $r = <<EOF
+
+<tr>
+    <td$bb><tt>$icon&nbsp;$modlink</tt></td>
+    <td$bb><tt>$bandwidth</tt></td>
+    <td$bb><tt>[<a$ab href=\"$WHOIS_SCRIPT?q=$address\">$netname</a>]</tt></td>
+</tr>
+
+EOF
+;
+    return $r;
+}
+
+# parse the URI parameters
+
+if($ENV{"REQUEST_URI"} && $ENV{"REQUEST_URI"} =~ /\?/) {
+    ($uri = $ENV{"REQUEST_URI"}) =~ s/.*\?//g;
+}
+
+my @prompts = split /&/, $uri;
+
+foreach (@prompts) {
+    my ($k, $v) = split /=/, $_;
+    $uri_fields{$k} = $v;
+}
+
+# parse file containing country codes
+
+open F, "<$F_CCODES" || warn "country code mapping not available";
+while(<F>) {
+    if(!/^#/) {
+        $ccode{$1} = $2 if /^(\S+)\s+(.+)$/;
+    }
+}
+close F;
+
+# compose the header and navigation links
+
+$response = <<EOF
+Content-type: text/html
+
+<!doctype html public "-//W3C//DTD HTML 4.01//EN"
+    "http://www.w3.org/TR/html4/strict.dtd";>
+<html>
+<head>
+<title>$TITLE</title>
+<meta name="Author" content="Geoffrey Goodell">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<meta http-equiv="Content-Style-Type" content="text/css">
+<link rel="stylesheet" type="text/css" href="http://serifos.eecs.harvard.edu/style.css";>
+</head>
+
+<body>
+
+<h2>Blossom User Interface</h2>
+
+EOF
+;
+
+# parse HTTP POST data, if available
+
+$method = $ENV{"REQUEST_METHOD"};
+
+if($method eq "POST") {
+    read(STDIN, $_, $ENV{'CONTENT_LENGTH'});
+
+    my %addr        = ();
+    my %bn          = ();
+    my %bw          = ();
+    my %fields      = ();
+    my %net         = ();
+
+    my @prompts     = split /&/, $_;
+    my @urls        = ();
+
+    my $b_inst      = "";
+    my $cname       = "";
+    my $country     = "";
+    my $randomly    = "";
+    my $readentries = undef;
+
+    foreach (@prompts) {
+        my ($k, $v) = split /=/, $_;
+        $fields{$k} = $v;
+        if($k =~ /^([A-Z0-9][A-Z0-9])$/) {
+            $country = $1;
+            $cname = $ccode{$country};
+        } elsif($k eq $BLOSSOM_TAG) {
+            $country = $BLOSSOM_TAG;
+            $cname = $BLOSSOM_TEXT;
+        }
+    }
+
+    $response .= "<p>You have requested: <b>$cname</b></p>\n";
+
+    if($uri_fields{"proxy"}) {
+        $response .= "<p>You have requested: <b>Implicit Proxy</b></p>\n";
+    }
+
+    my $modurl = modify_url($fields{"url"}, "q.c-$country.blossom");
+
+    if($country eq $BLOSSOM_TAG) {
+        push @urls, "$URL_EXIT$BLOSSOM";
+    } else {
+        push @urls, "$URL_EXIT$STATUS";
+        push @urls, "$URL_EXIT$BLOSSOM";
+        $b_inst      = "  Blossom nodes are shown in red and marked with an asterisk (*).";
+        $randomly    = " or <a href=\"$modurl\">instruct Blossom to randomly select a node instead</a>";
+    }
+
+    $response .= <<EOF
+
+<p>Please select the Tor node from which you wish to connect to the remote
+website$randomly.  Click the network name to view the corresponding WHOIS
+record.$b_inst</p>
+
+EOF
+;
+
+    foreach my $url (@urls) {
+        my $b = 0;
+        if($url eq "$URL_EXIT$BLOSSOM" and $country ne $BLOSSOM_TAG) {
+            $b = 1;
+        }
+        open W, "$WGET \"$url\" |" || warn "node status not available";
+        while(<W>) {
+            if(/^(\S+)\s+(\S+)\s+(\S+)\s+\S+\s+(\S+)\s+(\S+)\s+(\S+)$/) {
+                my ($cc, $router, $bandwidth, $address, $netname, $port)
+                        = ($1, $2, $3, $4, $5, $6);
+                if($port ne "-"
+                        and ($country eq $BLOSSOM_TAG
+                        or ($country eq $cc
+                        and ($b
+                        or ($router !~ /^\*/
+                        and $bandwidth >= $V1_MINBW*1000))))) {
+                    $router =~ s/^\*//;
+                    $bw{$router}    = $bandwidth;
+                    $addr{$router}  = $address;
+                    $net{$router}   = $netname;
+                    $bn{$router}    = $b or 0;
+                }
+            }
+        }
+        close W;
+    }
+
+    unless($fields{"url"} and $fields{"url"} ne "") {
+        $response .= "<p><span class=\"heading\">ERROR:</span> URL not specified.</p>\n";
+    }
+
+    $response .= "<table>\n\n";
+
+    foreach my $router (sort keys %bw) {
+        my $modurl = modify_url($fields{"url"}, "$router.exit");
+        $response .= report_router($router, $addr{$router}, $bw{$router}, $net{$router}, $modurl, $bn{$router})
+    }
+    $response .= <<EOF
+
+</table>
+
+<p><a href="">return to main page</a></p>
+
+EOF
+;
+} else {
+    # POST data is unavailable
+
+    my %addr        = ();
+    my %bw          = ();
+    my %fields      = ();
+    my %net         = ();
+
+    my $readentries = "";
+
+    $response .= <<EOF
+
+<p>Blossom allows users to access a wide range of Internet resources from the
+perspective of participating <a href="http://tor.eff.org/";>Tor</a> exit nodes,
+including nodes on the Tor overlay network as well as nodes on the
+independently-constructed Blossom overlay network, which supports arbitrary
+underlying network topologies.  For detailed information about the current
+state of the Tor network, consult the <a href="$URL_EXIT">Tor Exit Node
+Status</a> page.</p>
+EOF
+;
+
+    if($uri_fields{"proxy"}) {
+        $response .= <<EOF
+
+<p><span class="heading">Step-1</span> Consider <a href="$URL_SELF">manually
+configuring your own proxy settings</a> rather than using our proxy
+implicitly.</p>
+EOF
+;
+    } else {
+        $response .= <<EOF
+
+<p><span class="heading">Step-1</span> Configure your browser to use the HTTP
+proxy running on <b>cassandra.eecs.harvard.edu:8119</b>.  If you do not know
+how to do this, then please either <a
+href="http://www.idmask.com/en/help_changing_proxy_fox.html";>determine how to
+change your browser proxy settings</a> or <a
+href="$URL_SELF?proxy=1">implicitly use our proxy instead</a>.</p>
+EOF
+;
+    }
+
+    $response .= <<EOF
+
+<p><span class="heading">Step-2</span> Provide a URL to access via Blossom.</p>
+
+<form action="" method="post">
+
+<p><b>URL:</b>&nbsp;<input type="text" name="url" size="64" maxlength="256"></p>
+
+<p><span class="heading">Step-3a</span> To view the web resource using a
+Blossom proxy, please choose the following option:</p>
+
+<p><input type=\"submit\" name=\"$BLOSSOM_TAG\" value=\"Select a Blossom Node\"></p>
+
+<p><b>- OR -</b></p>
+
+<p><span class="heading">Step-3b</span> To select a node by country from either
+the Tor network or the Blossom network, click the corresponding flag:</p>
+
+<table>
+
+EOF
+;
+
+    # determine countries with acceptable exit nodes
+
+    foreach my $url ("$URL_EXIT$STATUS", "$URL_EXIT$BLOSSOM") {
+        my $b = 1 if $url eq "$URL_EXIT$BLOSSOM";
+        open W, "$WGET \"$url\" |" || warn "node status not available";
+        while(<W>) {
+            if(/^(\S+)\s+(\S+)\s+(\S+)\s+\S+\s+\S+\s+\S+\s+(\S+)$/) {
+                my ($country, $router, $bandwidth, $port) = ($1, $2, $3, $4);
+                unless(($port eq "-")
+                        or ((not $b) and $router =~ /^\*/)
+                        or ((not $b) and $bandwidth < $V1_MINBW*1000)) {
+                    $nodes{$country}++
+                }
+            }
+        }
+        close W;
+    }
+
+    foreach my $country (sort keys %nodes) {
+        my $nn = $nodes{$country};
+        if($nn > 0) {
+            (my $cc = $ccode{$country}) =~ s/ /&nbsp;/;
+            (my $cy = $country) =~ y/A-Z/a-z/;
+
+            $response .= "<tr>\n";
+            $response .= "    <td><input type=\"image\" name=\"$country\" value=\"1\" src=\"$URL_FLAGS/$cy\" alt=\"$country\">&nbsp;$cc&nbsp;&nbsp;</td>\n";
+            $response .= "    <td class=\"number\">$nn</td>\n";
+            $response .= "</tr>\n";
+        }
+    }
+
+    $response .= <<EOF
+
+</table>
+</form>
+
+<p>
+    [<a href="$URL_SOURCE">source&nbsp;code</a>]
+    [<a href="$URL_HOME">Blossom&nbsp;home&nbsp;page</a>]
+</p>
+
+EOF
+;
+}
+
+$response .= <<EOF
+
+<hr>
+
+<p><a href="http://validator.w3.org/check?uri=http%3A%2F%2Fserifos.eecs.harvard.edu%2Fcgi-bin%2Fblossom.pl";><img src="http://validator.w3.org/images/vh401.gif"; alt="valid HTML 4.01"/></a></p>
+
+<p><a href="http://jigsaw.w3.org/css-validator/validator?uri=http%3A%2F%2Fserifos.eecs.harvard.edu%2Fcgi-bin%2Fexit.pl";><img src="http://jigsaw.w3.org/css-validator/images/vcss"; alt="valid CSS"/></a></p>
+
+</body></html>
+
+EOF
+;
+
+# cache the result
+
+if($cachefile) {
+    open C, ">$CACHE/$cachefile" || die;
+    print C $response;
+    close C;
+}
+
+# output the result
+
+print $response;
+exit 0;
+


Property changes on: blossom/trunk/blossom.pl
___________________________________________________________________
Name: svn:executable
   + *

Added: blossom/trunk/blossom.py
===================================================================
--- blossom/trunk/blossom.py	                        (rev 0)
+++ blossom/trunk/blossom.py	2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,3184 @@
+#!/usr/bin/env python
+# $Id: blossom.py,v 1.164 2006-06-01 03:00:16 goodell Exp $
+
+__license__ = """
+Copyright (c) 2005-2006 Geoffrey Goodell.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+    * Neither the names of the copyright owners nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+"""
+
+__version__ = "0.2.11"
+
+__all__ = ["BlossomError",
+           "ClientRequestHandler",
+           "DirectoryRequestHandler",
+           "TorEventHandler",
+           "TorCloseHandler"]
+
+import getopt
+import httplib
+import os
+import random
+import re
+import select
+import signal
+import socket
+import string
+import struct
+import sys
+import threading
+import time
+
+from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+from SocketServer import ThreadingMixIn
+from TorCtl import *
+
+# constants
+
+PATH                = 0
+BUILT               = 1
+STREAMS             = 2
+
+T_CHECK             = 0
+T_METADATA          = 1
+T_GET               = 2
+T_PUBLISH           = 3
+T_PERIODIC          = 4
+
+LEFT                = 0
+RIGHT               = 1
+
+SUCCESS             = 0
+TARGET              = 1
+
+DESC                = 1
+META                = 2
+
+LOGLEVEL            = ("err", "warn", "notice", "info")
+
+# timing options
+
+TIME_ACTION         = 0.5           # time to wait following an action
+TIME_BLOSSOM        = 600           # interval for performing Blossom updates
+TIME_CACHE          = 600           # interval for fetching router lists
+TIME_CHECK          = 1200          # interval between periodic consistency checks
+TIME_POLICY         = 7200          # interval between exit policy checks
+TIME_RETRY          = 5             # interval for connection retries
+TIME_REFRESH        = 1             # interval for HTML refreshes
+TIME_SAVE           = 10            # how long to keep track of closed streams
+
+DIR_DESC_EXPIRATION = 3600          # when to republish descriptors
+DIR_DESC_DELETE     = 86400         # when to delete descriptors
+DIR_PEER_KEEPALIVE  = 180           # when to conclude that a peer is dead
+DIR_POLL_INTERVAL   = 60            # interval for polling directory neighbors
+
+TIMEOUT             = 30            # timeout for select loops
+
+# configuration options
+
+BUFFER_SIZE         = 16384
+CONNECTION_CLOSED   = 0
+DEBUG               = 0
+DIRPORT             = 0
+DISCLOSE_TARGET     = 1             # explicit query rather than generic download
+ENABLE_DIR          = 0
+MAXINT              = 4294967295    # a sufficiently large integer
+MAXLEN              = 16            # maximum length of blossom-path
+MAXNICKLEN          = 20            # maximum length of router nickname
+MAXREATTACH         = 16            # maximum number of times to reattach a stream
+MAXRETRY            = 60            # maximum retry interval (seconds)
+
+BLOSSOM             = []
+NICK                = ""
+
+WEB_STATUS          = "serifos.exit"
+BLOSSOM_ARGS        = "blossom=lefkada&"
+POLICY_ARGS         = "addr=1&textonly=1&ports"
+STATUS_ARGS         = "addr=1&textonly=fingerprint"
+
+HTTP_PROXY          = "localhost:8118"
+DIR_SERVER          = "0.0.0.0:9030"
+TORCONTROL          = "localhost:9051"
+SERVER              = "localhost:9052"
+
+IMG_SIZE            = "width=18 height=12"
+URL_FLAGS           = "/flags"
+URL_ICONS           = "/icons"
+ICON_BLANK          = "%s/v0.gif" % URL_ICONS
+ICON_BUILT_0        = "%s/s0.gif" % URL_ICONS
+ICON_BUILT_1        = "%s/s1.gif" % URL_ICONS
+ICON_SMITE          = "%s/ur.gif" % URL_ICONS
+ICON_UNBUILT        = "%s/hn.gif" % URL_ICONS
+ICON_V0             = "%s/v0.gif" % URL_ICONS
+ICON_V1             = "%s/v1.gif" % URL_ICONS
+ICON_V2             = "%s/v2.gif" % URL_ICONS
+ICON_V3             = "%s/v3.gif" % URL_ICONS
+
+# global variables
+
+AUTOREFRESH         = 0
+INIT                = 0
+PERSIST             = 0
+META_LOCAL          = [__version__]
+
+addr                = {}
+attempted           = {}
+bw                  = {}
+cc                  = {}
+cc_name             = {}
+circuits            = {}
+closed_streams      = {}
+counted_streams     = {}
+detached_streams    = {}
+failed_streams      = {}
+fingerprint         = {}
+local               = {}
+network             = {}
+path                = {}
+pending_streams     = {}
+policy              = {}
+policy_time         = {}
+port                = {}
+prop                = {}
+query_streams       = {}
+received_path       = {}
+semaphore           = {}
+streams             = {}
+tor_nodes           = {}
+
+interesting_ports   = []
+persist_nickname    = {}
+persist_id          = {}
+queue               = ""
+threads             = {}
+unestablished       = {}
+
+# data for individual routers
+
+desc                = {}
+metadata            = {}
+router_adv          = {}
+update_time         = {}
+
+dir_fingerprint     = {}
+dir_metadata        = {}
+dir_path            = {}
+dir_summary         = {}
+
+# data for directory peers
+
+full                = {}
+selection           = {}
+dir_port            = {}
+dir_prop            = {}
+dir_proxy           = {}
+metadata_pending    = {}
+neighbors_recv      = {}
+neighbors_send      = {}
+summary             = {}
+summary_pending     = {}
+summary_remote      = {}
+
+class BlossomError(Exception): pass
+class MaxReattachError(Exception): pass
+class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): pass
+
+class HTTPPostThread(threading.Thread):
+    def __init__(self, target, uri, payload, timeout=1):
+        self.target = target
+        self.uri = uri
+        self.payload = payload
+        self.timeout = timeout
+        self._stopevent = threading.Event()
+
+        threading.Thread.__init__(self)
+
+    def run(self):
+        name = self.getName()
+        threads[name] = "HTTPPost"
+        log_msg(3, "*** THREAD: %s %s" % (threads[name], name))
+
+        try:
+            dh, dp = self.target.split(":")
+            h = httplib.HTTP(dh, dp)
+            h.putrequest("POST", self.uri)
+            h.putheader("Content-length", "%d" % len(self.payload))
+            h.endheaders()
+            h.send(self.payload)
+            ec, em, headers = h.getreply()
+            log_msg(2, "--> HTTP-POST SUCCEEDED requesting %s from %s:%s [%s]" \
+                % (self.uri, dh, dp, len(self.payload)))
+        except socket.error:
+            log_msg(1, "HTTP-POST FAILED requesting %s from %s:%s" % (self.uri, dh, dp))
+        except:
+            log_msg(1, "HTTP-POST unexpected: %s" % sys.exc_info()[0])
+
+        del threads[name]
+
+    def join(self, timeout=None):
+        self._stopevent.set()
+        threading.Thread.join(self, timeout)
+
+class OpenURLThread(threading.Thread):
+    def __init__(self, dh, dp, url, policy=-1, timeout=1):
+        self.dh = dh
+        self.dp = dp
+        self.url = url
+        self.policy = policy
+        self.timeout = timeout
+        self._stopevent = threading.Event()
+
+        threading.Thread.__init__(self)
+
+    def run(self):
+        name = self.getName()
+        threads[name] = "OpenURL"
+        log_msg(3, "*** THREAD: %s %s" % (threads[name], name))
+
+        try:
+            h = httplib.HTTP(self.dh, self.dp)
+            h.putrequest('GET', self.url)
+            h.endheaders()
+            errcode, errmsg, headers = h.getreply()
+            log_msg(2, "<-- HTTP-GET %s:%s %s: %s" % (self.dh, self.dp, self.url, str(errcode)))
+            h = h.file
+            if self.policy < 0:
+                process_descriptors(h, "%s:%s" % (self.dh, self.dp))
+            else:
+                while h:
+                    try:
+                        line = h.readline()
+                    except AttributeError, e:
+                        log_msg(1, repr(e))
+                        break
+                    except EOFError, e:
+                        log_msg(1, repr(e))
+                        break
+                    if not line:
+                        break
+                    elif line:
+                        if self.policy:
+                            line = "POLICY %s" % line
+                            log_msg(3, "--- %s" % line[:-1])
+                        else:
+                            if self.url == STATUS_URL_BLOSSOM:
+                                line = "DATA+ %s" % line
+                            else:
+                                line = "DATA %s" % line
+                            log_msg(3, "--- %s" % line[:-1])
+                        process_line(line)
+
+        except AttributeError, e:
+            log_msg(1, "AttributeError")
+        except socket.error, (ec, em):
+            log_msg(1, "socket.error %s %s" % (repr(ec), repr(em)))
+        except IOError, (ec, em):
+            log_msg(1, "IOError %s %s" % (repr(ec), repr(em)))
+
+        del threads[name]
+
+    def join(self, timeout=None):
+        self._stopevent.set()
+        threading.Thread.join(self, timeout)
+
+class SearchThread(threading.Thread):
+    def __init__(self, streamID, dest, target, timeout=1):
+        self.streamID = streamID
+        self.dest = dest
+        self.target = target
+        self.timeout = timeout
+        self._stopevent = threading.Event()
+
+        threading.Thread.__init__(self)
+
+    def run(self):
+        name = self.getName()
+        threads[name] = "Search"
+        log_msg(3, "*** THREAD: %s %s" % (threads[name], name))
+
+        search(self.streamID, self.dest, self.target)
+        log_msg(3, "search complete")
+
+        del threads[name]
+
+    def join(self, timeout=None):
+        self._stopevent.set()
+        threading.Thread.join(self, timeout)
+
+class PeriodicClientThread(threading.Thread):
+    def __init__(self, last, interesting_ports, timeout=1):
+        self.last = last
+        self.interesting_ports = interesting_ports
+        self.timeout = timeout
+        self._stopevent = threading.Event()
+
+        threading.Thread.__init__(self)
+
+    def run(self):
+        global CONNECTION_CLOSED
+        global INIT
+
+        name = self.getName()
+        threads[name] = "PeriodicClient"
+        log_msg(3, "*** THREAD: %s %s" % (threads[name], name))
+
+        last = self.last
+        interesting_ports = self.interesting_ports
+
+        # periodically fetch parsed Tor metadata
+
+        if time.time() - last[T_METADATA] > TIME_CACHE:
+            log_msg(3, "*** fetching Tor metadata")
+            last[T_METADATA] = time.time()
+
+            urllist = [STATUS_URL]
+            if BLOSSOM:
+                urllist.append(STATUS_URL_BLOSSOM)
+            if not BLOSSOM_ARGS:
+                urllist = []
+
+            for url in urllist:
+                obtain_tor_metadata(url)
+
+        threads[name] = "PeriodicClient (phase 1)"
+
+        # periodically perform consistency check
+
+        if DEBUG and time.time() - last[T_CHECK] > TIME_CHECK:
+            last[T_CHECK] = time.time()
+            process_line("CONSISTENCY\n")
+
+        threads[name] = "PeriodicClient (phase 2)"
+
+        # periodically perform Blossom updates
+
+        if BLOSSOM and time.time() - last[T_GET] > TIME_BLOSSOM:
+            last[T_GET] = time.time()
+            for b in BLOSSOM:
+                try:
+                    get_descriptors(b)
+                except socket.error, (ec, em):
+                    log_msg(1, "socket.error %s %s" % (repr(ec), repr(em)))
+
+        if INIT and BLOSSOM and time.time() - last[T_PUBLISH] > TIME_BLOSSOM:
+            last[T_PUBLISH] = time.time()
+            for b in BLOSSOM:
+                try:
+                    if NICK != "":
+                        publish_descriptor(b)
+                except socket.error, (ec, em):
+                    log_msg(1, "socket.error %s %s" % (repr(ec), repr(em)))
+
+        threads[name] = "PeriodicClient (phase 3)"
+
+        # establish initial persistent connections
+
+        if PERSIST:
+            to_establish = unestablished.keys()
+            log_msg(3, "--- BLOSSOM: %s" % BLOSSOM)
+            log_msg(3, "--- to_establish: %s" % to_establish)
+            for b in to_establish:
+                establish_persistent_connection(b)
+
+        if CONNECTION_CLOSED > 0:
+            conn = getConnection()
+            if conn:
+                CONNECTION_CLOSED = 0
+
+        for qp in interesting_ports:
+            get_tor_policy(qp)
+
+        threads[name] = "PeriodicClient (phase 4)"
+
+        if not ENABLE_DIR:
+            INIT = 1
+
+        del threads[name]
+
+        # report list of presently active threads
+
+        for thread in threads.keys():
+            try:
+                log_msg(3, "--- active: %s %s" % (thread, threads[thread]))
+            except:
+                # key deletion race condition: not critical
+                pass
+
+    def join(self, timeout=None):
+        self._stopevent.set()
+        threading.Thread.join(self, timeout)
+
+class PeriodicDirectoryThread(threading.Thread):
+    def __init__(self, queue, timeout=1):
+        self.queue = queue
+        self.timeout = timeout
+        self._stopevent = threading.Event()
+
+        threading.Thread.__init__(self)
+
+    def run(self):
+        name = self.getName()
+        threads[name] = "PeriodicDirectory"
+        log_msg(3, "*** THREAD: %s %s" % (threads[name], name))
+
+        DIR_REG_LOCK = 1
+        send_updates(self.queue)
+        DIR_REG_LOCK = 0
+
+        del threads[name]
+
+    def join(self, timeout=None):
+        self._stopevent.set()
+        threading.Thread.join(self, timeout)
+
+class DirectoryServiceThread(threading.Thread):
+    def __init__(self, timeout=1):
+        self.timeout = timeout
+        self._stopevent = threading.Event()
+
+        threading.Thread.__init__(self)
+
+    def run(self):
+        global INIT
+
+        name = self.getName()
+        threads[name] = "DirectoryService"
+        log_msg(3, "*** THREAD: %s %s" % (threads[name], name))
+
+        subthreads = []
+
+        for target in neighbors_recv.keys():
+            thread = GetBurstThread(target)
+            thread.setDaemon(1)
+            thread.start()
+            subthreads.append(thread)
+
+        # start the directory-side web server
+        DirectoryRequestHandler.protocol_version = "HTTP/1.0"
+        dir_httpd = ThreadingHTTPServer(('', DIR_PORT), DirectoryRequestHandler)
+        sa = dir_httpd.socket.getsockname()
+
+        INIT = 1
+        log_msg(2, "*** serving HTTP on %s:%s." % (sa[0], sa[1]))
+
+        handle_callbacks_individually(dir_httpd, processing_dir=1)
+
+        del threads[name]
+
+    def join(self, timeout=None):
+        self._stopevent.set()
+        threading.Thread.join(self, timeout)
+
+class GetBurstThread(threading.Thread):
+    def __init__(self, target, timeout=1):
+        self.target = target
+        self.timeout = timeout
+        self._stopevent = threading.Event()
+
+        threading.Thread.__init__(self)
+
+    def run(self):
+        name = self.getName()
+        threads[name] = "GetBurst"
+        log_msg(3, "*** THREAD: %s %s" % (threads[name], name))
+
+        log_msg(2, "*** BURST PHASE 1: %s" % self.target)
+
+        if neighbors_send.has_key(self.target):
+            log_msg(2, "DIR BURST: %s" % self.target)
+            dh, dp = neighbors_send[self.target]
+            selector = "/blossom/burst"
+            try:
+                h = httplib.HTTP(dh, dp)
+                h.putrequest('GET', selector)
+                h.endheaders()
+                errcode, errmsg, headers = h.getreply()
+                log_msg(2, "<-- HTTP-GET %s:%s result: %s" % (dh, dp, str(errcode)))
+            except socket.error:
+                log_msg(1, "HTTP-GET FAILED connecting to %s:%s" % (dh, dp))
+                log_msg(1, "DIR BURST %s:%s FAILED" % (dh, dp))
+                return
+
+            log_msg(2, "DIR BURST %s:%s PROCEEDING" % (dh, dp))
+            queue = ""
+            while 1:
+                try:
+                    line = h.file.readline()
+                except AttributeError, e:
+                    log_msg(1, "%s" % repr(e))
+                    break
+                except EOFError, e:
+                    log_msg(1, "%s" % repr(e))
+                    break
+                if not line:
+                    break
+
+                # remove \r characters
+
+                m = re.search(r'^(.*)\r$', line)
+                if m:
+                    line = m.group(1)
+
+                queue += line
+
+            entries = parse_queue(queue)
+            lines = ["directory-update %s %s" % (self.target, dp)]
+            log_msg(2, "<-- BURST %s:%s [%s]" % (dh, dp, len(queue)))
+
+            for node in entries.keys():
+                tokens = entries[node]
+
+                if tokens.has_key("summary"):
+                    log_msg(3, "%s" % tokens["summary"])
+                    lines.append(tokens["summary"])
+
+                if tokens.has_key("compiled-metadata"):
+                    log_msg(3, "%s" % tokens["compiled-metadata"])
+                    lines.append(tokens["compiled-metadata"])
+
+                if tokens.has_key("directory"):
+                    log_msg(3, "%s" % tokens["directory"])
+                    lines.append(tokens["directory"])
+
+                if tokens.has_key("router"):
+                    log_msg(3, "router %s" % node)
+                    lines.extend(tokens["router"].split("\n"))
+
+                    if not tokens.has_key("blossom-path"):
+                        tokens["blossom-path"] = "blossom-path %s" % node
+                    log_msg(3, "%s" % tokens["blossom-path"])
+                    lines.append(tokens["blossom-path"])
+
+                    if tokens.has_key("metadata"):
+                        log_msg(3, "%s" % tokens["metadata"])
+                        lines.append(tokens["metadata"])
+
+                    if not tokens.has_key("router-advertisement"):
+                        tokens["router-advertisement"] = "router-advertisement %s" % node
+                    log_msg(3, "%s" % tokens["router-advertisement"])
+                    lines.append(tokens["router-advertisement"])
+
+            parse_update(lines)
+            log_msg(2, "DIR BURST %s:%s COMPLETED" % (dh, dp))
+
+        log_msg(2, "*** BURST PHASE 1: %s COMPLETED" % self.target)
+        log_msg(2, "*** BURST PHASE 2: %s" % self.target)
+        send_updates(generate_directory_report("", "/tor/"))
+        log_msg(2, "*** BURST PHASE 2: %s COMPLETED" % self.target)
+
+        del threads[name]
+
+    def join(self, timeout=None):
+        self._stopevent.set()
+        threading.Thread.join(self, timeout)
+
+class TorEventHandler(EventHandler):
+    def circ_status(self, eventtype, circID, status, path):
+        """Called when a circuit status changes if listening to CIRCSTATUS
+           events.  'status' is a member of CIRC_STATUS; circID is a numeric
+           circuit ID, and 'path' is the circuit's path so far as a list of
+           names.
+        """
+        global semaphore
+
+        curr_time = int(time.time())
+        text = "CIRC %s %s %s %s\n" % (curr_time, circID, status, ",".join(path))
+        log_msg(2, "CIRC %s %s %s" % (circID, status, ",".join(path)))
+        process_line(text)
+
+        if len(path) > 0:
+            exit = path[-1]
+        else:
+            exit = ""
+
+        if status == "BUILT":
+            log_msg(3, "--- pending_streams: %s" % repr(pending_streams))
+            semaphore[circID] = 1
+            if pending_streams.has_key(circID):
+                for streamID in pending_streams[circID]:
+                    attach_stream(streamID, circID)
+                del pending_streams[circID]
+
+        if status in ("FAILED", "CLOSED"):
+            if status == "FAILED":
+                semaphore[circID] = -1
+            if persist_id.has_key(circID):
+                establish_persistent_connection(persist_id[circID])
+            if pending_streams.has_key(circID):
+                for streamID in pending_streams[circID]:
+                    if query_streams.has_key(streamID):
+                        self.stream_status("STREAM", "DETACHED", streamID, query_streams[streamID])
+
+    def stream_status(self, eventtype, status, streamID, target, circID="0"):
+        """Called when a stream status changes if listening to STREAMSTATUS
+           events.  'status' is a member of STREAM_STATUS; streamID is a
+           numeric stream ID, and 'target' is the destination of the stream.
+        """
+        global conn
+
+        curr_time = int(time.time())
+        text = "STREAM %s %s %s %s %s\n" % (curr_time, status, streamID, target, circID)
+        log_msg(2, "STREAM %s %s %s %s" % (status, streamID, target, circID))
+        process_line(text)
+
+        if BLOSSOM and status in ("NEW", "NEWRESOLVE", "DETACHED"):
+            try:
+                dest = ""
+                fail = 0
+                query = 0
+
+                # avoid attempting to reattach the same stream infinitely many times
+                if status == "DETACHED":
+                    if not detached_streams.has_key(streamID):
+                        detached_streams[streamID] = 0
+                    detached_streams[streamID] += 1
+                    if detached_streams[streamID] > MAXREATTACH:
+                        conn.close_stream(streamID, 1)
+                        raise MaxReattachError
+
+                # preserve queries even if attachment failed previously
+                if query_streams.has_key(streamID):
+                    target = query_streams[streamID]
+                    log_msg(2, "--- target: %s" % target)
+
+                m = re.match(r'^([A-Za-z0-9-.]+\.)?q\.((([A-Za-z0-9-]+)\.)+)blossom:([0-9]+)$', target)
+                if m:
+                    circID = 0
+                    query = 1
+                    query_streams[streamID] = target
+
+                    rh = m.group(1)[:-1]
+                    query = m.group(2)[:-1].split(".")
+                    q_port = m.group(5)
+                    log_msg(2, "--- QUERY: %s (%s)" % (",".join(query), q_port))
+
+                    country = ""
+                    isp = ""
+
+                    for elt in query:
+                        m = re.match(r'^([a-z])-(.*)$', elt.lower())
+                        if m:
+                            k = m.group(1)
+                            v = m.group(2)
+                            if k == "c":
+                                country = v
+                            if k == "i":
+                                isp = v
+
+                    allrtrs = {}
+
+                    for rtr in cc.keys():
+                        allrtrs[rtr] = 1
+
+                    if country:
+                        log_msg(2, "*** requested country: %s" % country)
+                        for rtr in allrtrs.keys():
+                            if cc[rtr] != country:
+                                del allrtrs[rtr]
+
+                    if isp:
+                        log_msg(2, "*** requested ISP: %s" % isp)
+                        for rtr in allrtrs.keys():
+                            if network[rtr].lower() != isp:
+                                del allrtrs[rtr]
+
+                    log_msg(3, "--- allrtrs.keys(): %s" % allrtrs.keys())
+
+                    if status != "DETACHED":
+                        for c_circID in circuits.keys():
+                            if len(circuits[c_circID][PATH]):
+                                last_hop = circuits[c_circID][PATH][-1].lower()
+                                log_msg(3, "--- last_hop: %s" % last_hop)
+                                if allrtrs.has_key(last_hop):
+                                    circID = c_circID
+                                    dest = last_hop
+                                    break
+                    if status == "DETACHED" or not dest:
+                        dest = select_random(streamID, allrtrs.keys(), q_port)
+                        interesting_ports.append(q_port)
+
+                    if dest:
+                        log_msg(2, "--- selected destination: %s" % dest)
+                        target = rh
+                        conn.redirect_stream(streamID, target)
+                    else:
+                        fail = 1
+
+                m = re.search(r'^(.*\.)?([A-Za-z0-9-]+)\.exit(:[0-9]+)?$', target)
+                if m:
+                    dest = m.group(2)
+                    if fingerprint.has_key(dest):
+                        log_msg(2, "*** converting fingerprint %s -> %s" % (dest, fingerprint[dest]))
+                        dest = fingerprint[dest]
+
+                # test for unconverted fingerprints
+                if len(dest) > MAXNICKLEN and re.match(r'^[0-9A-F]+$', dest):
+                    dest = ""
+
+                log_msg(2, "--- normal destination: %s" % dest)
+
+                # exit to Blossom node
+                if dest and INIT and "%s.exit" % dest != WEB_STATUS and not tor_nodes.has_key(dest):
+                    circID = 0
+                    if not dest:
+                        dest = m.group(1)
+
+                    log_msg(2, "*** BLOSSOM: requested circuit to %s" % dest)
+
+                    for c_circID in circuits.keys():
+                        if len(circuits[c_circID][PATH]) and circuits[c_circID][PATH][-1] == dest:
+                            circID = c_circID
+                            break
+
+                    log_msg(3, "--- INIT: %s, circID: %s, dest: %s" % (INIT, circID, dest))
+
+                    if INIT and not circID and dest != NICK:
+                        log_msg(3, "--- local: %s" % repr(local))
+                        log_msg(3, "--- path: %s" % repr(path))
+                        if not local.has_key(dest):
+                            log_msg(2, "*** missing descriptor for router: %s" % dest)
+                            log_msg(3, "--- summary.keys(): %s" % summary.keys())
+                            text = ""
+
+                            thread = SearchThread(streamID, dest, target)
+                            thread.setDaemon(1)
+                            thread.start()
+                            circID = -1
+
+                    if not circID and dest != NICK:
+                        try:
+                            seq = []
+                            if path.has_key(dest):
+                                log_msg(2, "*** path[%s]: %s" % (dest, path[dest]))
+                                for rtr in path[dest]:
+                                    if rtr != NICK:
+                                        seq.append(rtr)
+
+                            seq.append(dest)
+                            circID = conn.extend_circuit(circID, seq)
+                        except ErrorReply, e:
+                            log_msg(1, "%s" % e)
+                    if circuits.has_key(circID) and circuits[circID][BUILT]:
+                        log_msg(2, "*** BLOSSOM: A attaching %s to %s" % (streamID, circID))
+
+                        log_msg(2, "*** target: %s" % target)
+                        m = re.search(r'^(.*\.)?([A-Za-z0-9-]+)\.exit:([0-9]+)?$', target)
+                        if m:
+                            cur_addr = m.group(1)
+                            cur_exit = m.group(2)
+                            try:
+                                if m.group(1) and cur_addr:
+                                    conn.redirect_stream(streamID, cur_addr[:-1])
+                                elif addr.has_key(cur_exit):
+                                    conn.redirect_stream(streamID, addr[cur_exit])
+                            except ErrorReply:
+                                log_msg(1, "cannot redirect stream %s" % streamID)
+                        attach_stream(streamID, circID)
+
+                    elif circID:
+                        if not pending_streams.has_key(circID):
+                            pending_streams[circID] = []
+                        log_msg(2, "*** BLOSSOM: A queueing %s for attachment to %s" \
+                            % (streamID, circID))
+                        pending_streams[circID].append(streamID)
+                    elif circID == 0:
+                        log_msg(2, "*** BLOSSOM: cannot compose circuit for stream %s" % streamID)
+
+                elif circuits.has_key(circID) and circuits[circID][BUILT]:
+                    log_msg(2, "*** BLOSSOM: B attaching %s to %s" % (streamID, circID))
+                    attach_stream(streamID, circID)
+
+                elif query and dest:
+                    # query Blossom request
+                    log_msg(2, "*** BLOSSOM: received query %s %s %s" % (streamID, dest, target))
+                    log_msg(2, "--- circID: %s" % circID)
+                    if not int(circID):
+                        circID = conn.extend_circuit(0, [dest])
+                        log_msg(2, "--- circID: %s" % circID)
+                    if not pending_streams.has_key(circID):
+                        pending_streams[circID] = []
+                    log_msg(2, "*** BLOSSOM: B queueing %s for attachment to %s" % (streamID, circID))
+                    pending_streams[circID].append(streamID)
+
+                elif fail:
+                    log_msg(2, "*** BLOSSOM: cannot create circuit for stream %s" % streamID)
+                    conn.close_stream(streamID, 1)
+
+                else:
+                    # ordinary Tor request
+                    log_msg(2, "*** BLOSSOM: delegating management for stream %s" % streamID)
+                    attach_stream(streamID, 0)
+
+            except ErrorReply, e:
+                log_msg(1, "%s" % e)
+            except MaxReattachError:
+                log_msg(1, "MAXIMUM NUMBER OF REATTACHMENTS EXCEEDED")
+            except TorCtlClosed:
+                log_msg(1, "CONTROLLER CONNECTION CLOSED: %s" % repr(ex))
+                CONNECTION_CLOSED = 1
+            except:
+                log_msg(1, "stream_status unexpected: %s" % sys.exc_info()[0])
+            log_msg(2, "*** internal processing complete for stream %s" % streamID)
+
+        if status in ("FAILED", "CLOSED"):
+            if query_streams.has_key(streamID):
+                del query_streams[streamID]
+
+class ClientRequestHandler(BaseHTTPRequestHandler):
+    server_version = "Blossom/" + __version__
+
+    def do_GET(self):
+        global AUTOREFRESH
+
+        done = 0
+        output = ""
+
+        try:
+            if self.path[-4:] == ".css" or self.path[-4:] == ".gif":
+                try:
+                    f = open("%s%s" % (F_ROOT, self.path))
+                    data = f.read()
+                    self.send_response(200)
+                    if self.path[-4:] == ".css":
+                        self.send_header("Content-type", "text/css")
+                    else:
+                        self.send_header("ETag", "0-0-0-0")
+                        self.send_header("Content-type", "image/gif")
+                except:
+                    log_msg(1, "secondary do_GET A unexpected: %s" % sys.exc_info()[0])
+                    data = "404 File Not Found"
+                    self.send_response(404)
+                    self.send_header("Content-type", "text/plain")
+
+            elif self.path == "/":
+                AUTOREFRESH = 0
+                data = generate_output(AUTOREFRESH)
+                self.send_response(200)
+                self.send_header("Content-type", "text/html")
+
+            elif self.path == "/autorefresh":
+                AUTOREFRESH = -1
+                data = generate_output(AUTOREFRESH)
+                self.send_response(200)
+                self.send_header("Content-type", "text/html")
+
+            elif self.path == "/network-status":
+                data = generate_network_status()
+                self.send_response(200)
+                self.send_header("Content-type", "text/html")
+
+            elif len(self.path) > 9 and self.path[:10] == "/attach?q=":
+                data = generate_output(int(self.path[10:]))
+                self.send_response(200)
+                self.send_header("Content-type", "text/html")
+
+            elif len(self.path) > 8 and self.path[:9] == "/connect?":
+                vals = {}
+                args = self.path[9:].split("&")
+                for arg in args:
+                    k, v = arg.split("=")
+                    vals[k] = v
+
+                if vals["c"] and vals["s"]:
+                    try:
+                        sh, sp = parseHostAndPort(TORCONTROL)
+                        attach_stream(int(vals["s"]), int(vals["c"]))
+                    except:
+                        log_msg(1, "secondary do_GET B unexpected: %s" % sys.exc_info()[0])
+                        log_msg(2, "*** attach %s to %s unsuccessful" % (vals["s"], vals["c"]))
+
+                time.sleep(TIME_ACTION)
+
+                data = generate_output(AUTOREFRESH)
+                self.send_response(200)
+                self.send_header("Content-type", "text/html")
+
+            elif not done:
+                data = "404 File Not Found"
+                self.send_response(404)
+                self.send_header("Content-type", "text/plain")
+
+            if not done:
+                self.send_header("Content-Length", len(data))
+                self.end_headers()
+                self.wfile.write(data)
+
+        except KeyboardInterrupt:
+            log_msg(1, "exiting on ^C [%s]\n" % get_curr_time())
+            sys.exit(0)
+        except:
+            log_msg(1, "secondary do_GET C unexpected: %s" % sys.exc_info()[0])
+
+    def do_POST(self):
+        global conn
+
+        curr_time = time.time()
+        length = int(self.headers.getheader('content-length'), 10)
+        data = self.rfile.read(length)
+        lines = data.split("&")
+
+        circID = 0
+        for line in lines:
+            k, v = line.split("=")
+
+            # smite a circuit
+
+            if k == "c_smite":
+                circID = int(v)
+                try:
+                    # sh, sp = parseHostAndPort(TORCONTROL)
+                    conn.close_circuit(circID)
+                except:
+                    log_msg(1, "secondary do_POST A unexpected: %s" % sys.exc_info()[0])
+                    log_msg(2, "*** circuit smite %s unsuccessful" % circID)
+
+            # smite a stream
+
+            if k == "s_smite":
+                streamID = int(v)
+                try:
+                    # sh, sp = parseHostAndPort(TORCONTROL)
+                    conn.close_stream(streamID)
+                except:
+                    log_msg(1, "secondary do_POST B unexpected: %s" % sys.exc_info()[0])
+                    log_msg(2, "*** stream smite %s unsuccessful" % streamID)
+
+        time.sleep(TIME_ACTION)
+
+        data = generate_output(AUTOREFRESH)
+        self.send_response(200)
+        self.send_header("Content-type", "text/html")
+        self.send_header("Content-Length", len(data))
+        self.end_headers()
+        self.wfile.write(data)
+
+    def log_request(self, code='-', size='-'):
+        pass
+
+class DirectoryRequestHandler(BaseHTTPRequestHandler):
+    server_version = "Blossom/" + __version__
+
+    def do_GET(self):
+        curr_time = time.time()
+        log_msg(2, "<-- HTTP-GET %s from %s:%s" \
+            % (self.path, self.client_address[0], self.client_address[1]))
+
+        # purge particularly old routers
+
+        for router in update_time.keys():
+            if curr_time - update_time[router] > DIR_DESC_DELETE:
+                del update_time[router]
+                if desc.has_key(router):
+                    del desc[router]
+                if dir_fingerprint.has_key(router):
+                    del dir_fingerprint[router]
+                if dir_path.has_key(router):
+                    del dir_path[router]
+                if metadata.has_key(router):
+                    del metadata[router]
+                if router_adv.has_key(router):
+                    del router_adv[router]
+
+        reap_disconnected_neighbors(curr_time)
+
+        target = "%s:%s" % (self.client_address[0], self.client_address[1])
+        s_path = self.path
+
+        output = generate_directory_report(target, s_path)
+
+        try:
+            length = len(output)
+            self.send_response(200)
+            self.send_header("Content-type", "text/plain")
+            self.send_header("Content-Length", length)
+            self.end_headers()
+            self.wfile.write(output)
+            log_msg(2, "<-- HTTP-GET %s from %s:%s COMPLETED [%s]" \
+                % (self.path, self.client_address[0], self.client_address[1], len(output)))
+        except:
+            log_msg(1, "<-- HTTP-GET %s from %s:%s FAILED: %s" \
+                % (self.path, self.client_address[0], self.client_address[1],
+                sys.exc_info()[0]))
+
+    def do_POST(self):
+        log_msg(3, "<-- HTTP-POST %s from %s:%s" \
+            % (self.path, self.client_address[0], self.client_address[1]))
+        try:
+            curr_time = time.time()
+            length = int(self.headers.getheader('content-length'), 10)
+            data = self.rfile.read(length)
+            lines = data.split("\n")
+            client_addr, client_port = self.client_address
+
+            reap_disconnected_neighbors(curr_time)
+
+            if self.path == "/blossom/":
+                parse_blossom(lines, "")
+            elif self.path == "/blossom/directory-update": # BLOSSOM DIRECTORY
+                parse_update(lines)
+            log_msg(2, "<-- HTTP-POST %s from %s:%s COMPLETED [%s]" \
+                % (self.path, self.client_address[0], self.client_address[1], length))
+        except KeyboardInterrupt:
+            log_msg(1, "exiting on ^C [%s]\n" % get_curr_time())
+            sys.exit(0)
+
+    def log_request(self, code='-', size='-'):
+        pass
+
+def TorCloseHandler(ex):
+    global CONNECTION_CLOSED
+
+    try:
+        raise ex
+    except TorCtlClosed:
+        log_msg(1, "*** CONTROLLER CONNECTION CLOSED: %s" % repr(ex))
+        CONNECTION_CLOSED = 1
+    except:
+        pass
+
+def log_msg(debugval, msg):
+    if DEBUG >= debugval:
+        print "%s [%s] %s" % (get_curr_time()[11:], LOGLEVEL[debugval], msg)
+
+def attach_stream(streamID, circID):
+    try:
+        conn.attach_stream(streamID, circID)
+    except ErrorReply, e:
+        e = "%s" % e
+        log_msg(1, "%s [%s][%s]" % (e, streamID, circID))
+        if re.search(r'^552 ', e):
+            pass
+        elif re.search(r'^555 ', e) and BLOSSOM:
+            conn.authenticate("")
+            conn.set_option("__leavestreamsunattached", "1")
+        else:
+            log_msg(1, "unknown error: ignoring")
+
+def blossom():
+    if BLOSSOM:
+        return "Blossom"
+    else:
+        return "Tor"
+
+def select_random(streamID, list, qp):
+    if policy_time.has_key(qp):
+        log_msg(2, "--- policy_time[%s]: %s" % (qp, policy_time[qp]))
+    log_msg(2, "--- candidates (before screening): %s" % len(list))
+
+    newlist = []
+    if policy_time.has_key(qp) and policy_time[qp] + TIME_POLICY > time.time():
+        for rtr in list:
+            if policy[qp].has_key(rtr):
+                newlist.append(rtr)
+        list = newlist
+
+    log_msg(2, "--- candidates (policy screening): %s" % len(list))
+
+    newlist = []
+    for rtr in list:
+        if not (attempted.has_key(streamID) and attempted[streamID].has_key(rtr)):
+            newlist.append(rtr)
+    list = newlist
+
+    log_msg(2, "--- candidates (full screening): %s" % len(list))
+
+    if list:
+        n = int(random.random()*len(list))
+        selection = list[n]
+        if not attempted.has_key(streamID):
+            attempted[streamID] = {}
+        attempted[streamID][selection] = 1
+        return selection
+    else:
+        return []
+
+def format_summary(one_summary):
+    rtr_list = []
+    for rtr in one_summary.keys():
+        rtr_list.append("%s=%s" % (rtr, one_summary[rtr]))
+    rtr_list.sort()
+    return ",".join(rtr_list)
+
+def stream_attach(streamID, attached):
+    if attached or closed_streams.has_key(streamID):
+        return streams[streamID]
+    else:
+        return "<a class=\"standard\" href=\"/attach?q=%s\">%s</a>" % (streamID, streams[streamID])
+
+def stream_status(streamID, message):
+    if closed_streams.has_key(streamID):
+        return "<tt>&nbsp;<img %s src=\"%s\">&nbsp;%s&nbsp;</tt>" \
+            % (IMG_SIZE, ICON_BLANK, message)
+    else:
+        return "<tt>&nbsp;<input type=\"image\" name=\"s_smite\" value=\"%s\" %s src=\"%s\" alt=\"ss\">&nbsp;%s&nbsp;</tt>" \
+            % (streamID, IMG_SIZE, ICON_SMITE, message)
+
+def sort_numerically(array):
+    for i in range(0,len(array)):
+        array[i] = int(array[i])
+    array.sort()
+    for i in range(0,len(array)):
+        array[i] = "%d" % array[i]
+    return array
+
+def get_curr_time():
+    curr_time = time.gmtime()
+    return "%04d-%02d-%02d %02d:%02d:%02d" % curr_time[0:6]
+
+def icon_built(circID):
+    if circuits.has_key(circID) and circuits[circID][BUILT]:
+        if len(circuits[circID][STREAMS]) == 0:
+            icon = ICON_BUILT_0
+        else:
+            icon = ICON_BUILT_1
+    else:
+        icon = ICON_UNBUILT
+
+    return "<input type=\"image\" name=\"c_smite\" value=\"%s\" %s src=\"%s\" alt=\"cs\">" % (circID, IMG_SIZE, icon)
+
+def icon_cc(path):
+    a = ""
+
+    for rtr in path:
+        ccs = "~~"
+        if cc.has_key(rtr.lower()):
+            ccs = cc[rtr.lower()]
+            icon = "<img %s src=\"%s/%s.gif\">&nbsp;" % (IMG_SIZE, URL_FLAGS, ccs)
+        else:
+            icon = "<img %s src=\"%s\">&nbsp;" % (IMG_SIZE, ICON_BUILT_1)
+        if cc_name.has_key(ccs):
+            icon = "<acronym title=\"%s\">%s</acronym>" % (cc_name[ccs], icon)
+        a += icon
+
+    return a
+
+def icon_bw(path):
+    a = ""
+    min = 1<<16
+    for rtr in path:
+        if bw.has_key(rtr) and bw[rtr.lower()] < min:
+            min = bw[rtr.lower()]
+    if min >= 400:
+        r = "<img %s src=\"%s\">" % (IMG_SIZE, ICON_V3)
+    elif min >= 60:
+        r = "<img %s src=\"%s\">" % (IMG_SIZE, ICON_V2)
+    elif min >= 10:
+        r = "<img %s src=\"%s\">" % (IMG_SIZE, ICON_V1)
+    else:
+        r = "<img %s src=\"%s\">" % (IMG_SIZE, ICON_V0)
+
+    if min == 1<<16:
+        min = "unknown"
+
+    return "<acronym title=\"%s kB/s\">%s</acronym>" % (min, r)
+
+def auto_link(b):
+    if b == -1:
+        return "<a href=\"/\">autorefresh stop</a>"
+    else:
+        return "<a href=\"/\">reload</a> <a href=\"/autorefresh\">autorefresh start</a>"
+
+def auto_meta(b):
+    if b == -1:
+        return "<meta http-equiv=\"refresh\" content=\"%s;url=/autorefresh\"\n" % TIME_REFRESH
+    else:
+        return ""
+
+def attach_link(streamID, circID, a):
+    if streamID > 0:
+        return "<a href=\"/connect?s=%s&c=%s\">%s</a>" % (streamID, circID, a)
+    else:
+        return a
+
+def generate_directory_report(target, s_path):
+    curr_time = time.time()
+    output = ""
+    s_pub = "published %s\n" % time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
+    s_status = "router-status"
+
+   # report relatively recent routers
+
+    for router in update_time.keys():
+        router_status = ""
+        if curr_time - update_time[router] > DIR_DESC_EXPIRATION:
+            router_status = "!"
+        s_status += " %s%s=$%s" % (router_status, router, dir_fingerprint[router])
+
+    s_status += "\n"
+
+    if s_path in ["/tor/", "/blossom/burst"]:
+        # provide list of descriptors
+
+        output += "unsigned-directory %s %s\n" % (NICK, __version__)
+        output += s_pub
+
+        output += "running-routers"
+        for router in dir_fingerprint.keys():
+            output += " %s" % router
+        output += "\n"
+
+        output += s_status
+
+        dirs = selection.keys()
+        dirs.sort()
+        for dir in dirs:
+            if dir_port.has_key(dir) and dir_prop[selection[dir]].has_key(dir):
+                output += "directory %s %s %s\n" \
+                    % (dir, dir_port[dir], ",".join(dir_prop[selection[dir]][dir]))
+            else:
+                log_msg(1, "dir_port[%s] or dir_prop[%s][%s] missing" \
+                    % (dir, selection[dir], dir))
+
+        dirs = dir_summary.keys()
+        dirs.sort()
+        for dir in dirs:
+            output += "summary %s %s\n" % (dir, format_summary(dir_summary[dir]))
+
+        dirs = dir_metadata.keys()
+        dirs.sort()
+        for dir in dirs:
+            output += "compiled-metadata %s %s\n" % (dir, ",".join(dir_metadata[dir]))
+        output += "\n"
+
+        if s_path == "/tor/":
+            rtrs = dir_path.keys()
+            rtrs.sort()
+            for rtr in rtrs:
+                if dir_path[rtr]:
+                    output += "blossom-path %s %s\n" % (rtr, ",".join(dir_path[rtr]))
+
+            rtrs = metadata.keys()
+            rtrs.sort()
+            for rtr in rtrs:
+                if metadata[rtr]:
+                    output += "metadata %s %s\n" % (rtr, ",".join(metadata[rtr]))
+
+            rtrs = router_adv.keys()
+            rtrs.sort()
+            for rtr in rtrs:
+                output += "router-advertisement %s %s\n" % (rtr, ",".join(router_adv[rtr]))
+
+            output += "\n"
+
+        if desc.has_key(NICK):
+            log_msg(3, "<-- sending router per request: %s" % router)
+            output += desc[NICK]
+
+        if s_path == "/tor/":
+            for router in desc.keys():
+                if router != NICK:
+                    log_msg(3, "<-- sending router per request: %s" % router)
+                    output += desc[router]
+
+    elif s_path == "/tor/running-routers":
+        # provide forwarder availability information
+
+        output += "running-routers\n"
+        output += s_pub
+        output += s_status
+    else:
+        m = re.match(r'^/blossom/([0-9A-Za-z]*)$', s_path)
+        if m:
+            # perform Blossom query
+
+            output = ""
+            target = m.group(1)
+
+            if len(target) <= MAXNICKLEN:
+                if desc.has_key(target):
+                    if dir_path.has_key(target):
+                        output += "blossom-path %s %s\n" \
+                            % (target, ",".join(dir_path[target]))
+                    if metadata.has_key(target):
+                        output += "metadata %s %s\n" \
+                            % (target, ",".join(metadata[target]))
+                    if router_adv.has_key(target):
+                        output += "router-advertisement %s %s\n" \
+                            % (target, ",".join(router_adv[target]))
+                    output += "\n%s" % desc[target]
+                else:
+                    relevant = {}
+                    for dir in dir_summary.keys():
+                        if dir_summary[dir].has_key(target):
+                            relevant[dir] = 1
+                            output += "summary %s %s\n" \
+                                % (dir, format_summary(dir_summary[dir]))
+                    for dir in relevant.keys():
+                        if desc.has_key(dir):
+                            if dir_path.has_key(dir):
+