[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):
+                                output += "blossom-path %s %s\n" \
+                                    % (dir, ",".join(dir_path[dir]))
+                            if metadata.has_key(dir):
+                                output += "metadata %s %s\n" \
+                                    % (dir, ",".join(metadata[dir]))
+                            if router_adv.has_key(dir):
+                                output += "router-advertisement %s %s\n" \
+                                    % (dir, ",".join(router_adv[dir]))
+                            if selection.has_key(dir):
+                                output += "directory %s %s %s\n" \
+                                    % (dir, dir_port[dir], ",".join(dir_prop[selection[dir]][dir]))
+                            output += "\n%s" % desc[dir]
+
+    output += "end\n\r"
+    return output
+
+def establish_persistent_connection(b):
+    global conn
+
+    if persist_nickname.has_key(b):
+        try:
+            log_msg(2, "*** establishing persistent connection: %s" % b)
+            ret = conn.extend_circuit(0, [persist_nickname[b]])
+            persist_id[ret] = b
+            if unestablished.has_key(b):
+                del unestablished[b]
+        except ErrorReply, e:
+            log_msg(1, "%s" % e)
+
+# publish my descriptor to the specified directory
+def publish_descriptor(b):
+    f = 1
+    postable = ""
+    resource = "desc/name/" + NICK
+
+    log_msg(3, "*** publish descriptor for %s to %s" % (NICK, b))
+
+    try:
+        postable += get_info(resource)[0]
+    except struct.error, e:
+        log_msg(1, "publish_descriptor struct.error %s" % repr(e))
+        return
+    except ProtocolError, (ec, em):
+        log_msg(1, "publish_descriptor %s" % em)
+        return
+    except AttributeError:
+        log_msg(1, "publish_descriptor AttributeError")
+        return
+
+    postable += "\n"
+
+    if PERSIST:
+        if persist_nickname.has_key(b):
+            postable += "blossom-path %s %s\n" % (NICK, persist_nickname[b])
+        else:
+            log_msg(2, "warning: %s not in %s" % (b, persist_nickname.keys()))
+            f = 0
+    else:
+        postable += "blossom-path %s\n" % NICK
+
+    if META_LOCAL:
+        postable += "metadata %s %s\n" % (NICK, ",".join(META_LOCAL))
+
+    # publish descriptor and applicable Blossom metadata and path information
+    if f:
+        thread = HTTPPostThread(b, "/blossom/", postable)
+        thread.setDaemon(1)
+        thread.start()
+
+        log_msg(2, "*** publish descriptor for %s to %s SUCCEEDED" % (NICK, b))
+    else:
+        log_msg(1, "*** publish descriptor for %s to %s FAILED" % (NICK, b))
+
+    return
+
+def report_streams(time, select_stream, s_streams, count):
+    content = ""
+    sorted_keys = sort_numerically(s_streams.keys())
+    attached = 0
+
+    for streamID in sorted_keys:
+        s_status = ""
+        if count:
+            attached = 1
+            s_status = "SUCCEEDED"
+        if streams.has_key(streamID) and not counted_streams.has_key(streamID):
+            td_class = ""
+            if int(streamID) == select_stream:
+                td_class = " class=\"heading\""
+                s_status = "SELECTED"
+            elif s_streams[streamID] == "SENTCONNECT":
+                td_class = " class=\"boldnormal\""
+                s_status = "SENTCONNECT"
+                attached = 0
+            if closed_streams.has_key(streamID):
+                if time - TIME_SAVE > closed_streams[streamID]:
+                    log_msg(3, "*** time %s closed at %s" % (time, closed_streams[streamID]))
+                    del streams[streamID]
+                    del closed_streams[streamID]
+                    if failed_streams.has_key(streamID):
+                        del failed_streams[streamID]
+                    continue
+                else:
+                    if failed_streams.has_key(streamID):
+                        td_class = " class=\"failedstream\""
+                        s_status = "FAILED"
+                    else:
+                        td_class = " class=\"closedstream\""
+                        s_status = "CLOSED"
+            content += """
+<tr>
+    <td%s>%s</td>
+    <td%s style="text-align:right"><tt>&nbsp;%s&nbsp;</tt></td>
+    <td%s><tt>&nbsp;%s&nbsp;</tt></td>
+</tr>
+""" % ( td_class,
+        stream_status(streamID, s_status),
+        td_class,
+        streamID,
+        td_class,
+        stream_attach(streamID, attached)
+    )
+
+            if count:
+                counted_streams[streamID] = 1
+
+    return content
+
+def generate_output(arg):
+    global circuits
+    global counted_streams
+    global pending_streams
+    global query_streams
+    global streams
+
+    counted_streams = {}
+    int_time = int(time.time())
+    curr_time = get_curr_time()
+    content = ""
+
+    select_stream = -1
+    refresh = 0
+
+    if arg == -1:
+        refresh = arg
+    elif arg > 0:
+        select_stream = arg
+
+    # report open circuits
+
+    s_circuits = circuits.keys()
+    s_circuits = sort_numerically(s_circuits)
+
+    for circID in s_circuits:
+        td_class = "dimleftentry"
+        if circuits.has_key(circID) and circuits[circID][BUILT]:
+            td_class = "leftentry"
+
+        content += """
+<tr>
+    <td class="%s"><tt>&nbsp;%s&nbsp;%s&nbsp;%s</tt></td>
+    <td class="%s" style="text-align:right"><tt>&nbsp;%s&nbsp;</tt></td>
+    <td class="%s"><tt>&nbsp;%s&nbsp;</tt></td>
+</tr>
+""" % (td_class,
+       icon_built(circID),
+       icon_bw(circuits[circID][PATH]),
+       icon_cc(circuits[circID][PATH]),
+       td_class,
+       circID,
+       td_class,
+       attach_link(select_stream, circID, ",".join(circuits[circID][PATH]))
+    )
+
+        # report streams associated with this circuit
+
+        content += report_streams(int_time, select_stream, circuits[circID][STREAMS], 1)
+
+    # report unattached streams
+
+    unattached_report = report_streams(int_time, select_stream, streams, 0)
+
+    if len(counted_streams.keys()) < len(streams.keys()):
+        content += """
+<tr>
+    <td class="dimleftentry"><tt>&nbsp;<img src="%s">&nbsp;<img src="%s">&nbsp;</tt></td>
+    <td class="dimleftentry"></td>
+    <td class="dimleftentry"><tt>&nbsp;[unattached]&nbsp;</tt></td>
+</tr>
+%s""" % (ICON_BLANK, ICON_BLANK, unattached_report)
+
+    output = """<!doctype html public "-//W3C//DTD HTML 4.01//EN"
+    "http://www.w3.org/TR/html4/strict.dtd";>
+<html>
+<head>
+
+<title>%s Client Status</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">
+%s<link rel="stylesheet" type="text/css" href="style.css">
+</head>
+
+<body>
+
+<h1>%s Client Status</h1>
+
+<p><tt>current time: <b>%s</b></tt></p>
+
+<form action="" method="post"><table>
+%s</table></form>
+
+<p><tt>%s version %s</tt></p>
+
+</body>
+
+</html>
+""" % ( blossom(),          \
+        auto_meta(refresh), \
+        blossom(),          \
+        curr_time,          \
+        content,            \
+        auto_link(refresh), \
+        __version__         \
+    )
+
+    return output
+
+def generate_network_status():
+    log_msg(2, "*** generating network status page")
+    try:
+        status_list = conn.get_info("dir/status/all")
+        server_list = conn.get_info("dir/server/all")
+    except ErrorReply, e:
+        log_msg(1, e)
+    except:
+        pass
+    data = status_list + server_list
+    return data
+
+def search(streamID, dest, target):
+    global semaphore
+    global summary_remote # WARNING: this is very very bad
+
+    seq = []
+    desc = {}
+    summary_remote = {}
+    fail = 0
+    circID = 0
+
+    try:
+        sh, sp = parseHostAndPort(HTTP_PROXY)
+    except:
+        log_msg(1, "must use HTTP proxy for queries")
+        return
+
+    log_msg(2, "*** search to %s" % dest)
+
+    ## -- THESIS EXPERIMENT SECTION --
+
+    explicit = ""
+    m = re.match(r'([0-9a-zA-Z]+)-e', dest)
+    if m:
+        log_msg(1, "EXPLICIT")
+        explicit = m.group(1)
+
+    num_hops = 0
+    query_server = 0
+    m = re.match(r'([0-9]+)-([qs])', dest)
+    if m:
+        if m.group(2) == 'q':
+            query_server = 1
+        num_hops = int(m.group(1))
+
+    # random nodeset 0
+
+    # target_array = ['80708b47', '80708b6a', '815d4438', '81aad6c0', '825c46fb', '82c240a3', '82cb7f28', '84e34a28', '84fc98c2']
+
+    # random nodeset 1
+
+    # target_array = ['8004240b', '8006c09e', '80708b6a', '80723f0e', '80df0671', '824b5753', '8441f067', '8441f068', '8a17cce8', '8ff88baa']
+    target_array = ['ithaca', 'ithaca', '80708b6a', '80723f0e', '80df0671', '824b5753', '8441f067', '8441f068', '8a17cce8', '8ff88baa']
+
+    ## -- END THESIS EXPERIMENT SECTION --
+
+    while not num_hops and not explicit and not desc.has_key(dest) and not fail:
+        chosen_dir = ""
+        min = MAXINT
+
+        if not dest in target_array:
+            for dir in summary.keys():
+                if summary[dir].has_key(dest) and not seq.__contains__(dir):
+                    log_msg(2, "--- prop.keys(): %s" % prop.keys())
+                    if prop.has_key(dir):
+                        if len(prop[dir]) < min:
+                            min = len(prop[dir])
+                            chosen_dir = dir
+                    elif seq:
+                        min = 0
+                        chosen_dir = dir
+
+            if not chosen_dir:
+                for dir in summary_remote.keys():
+                    if summary_remote[dir].has_key(dest) and not seq.__contains__(dir):
+                        log_msg(2, "--- prop.keys(): %s" % prop.keys())
+                        chosen_dir = dir
+
+        if chosen_dir == dest:
+            break
+        if not chosen_dir:
+            fail = 1
+        else:
+            log_msg(2, "*** selecting directory: %s" % chosen_dir)
+
+            seq.append(chosen_dir)
+            if not dest in target_array:
+                if prop.has_key(chosen_dir):
+                    reversed = prop[chosen_dir]
+                    reversed.reverse()
+                    for elt in reversed:
+                        seq.append(elt)
+
+            log_msg(2, "*** stream %s current route to %s: %s" % (streamID, dest, repr(seq)))
+            log_msg(3, "--- port: %s" % repr(port))
+
+            url = ""
+
+            if port.has_key(chosen_dir):
+                chosen_server = "%s:%s" % (chosen_dir, port[chosen_dir])
+                url = "http://%s.exit:%s/blossom/%s"; % (chosen_dir, port[chosen_dir], dest)
+            else:
+                log_msg(1, "NO PORT AVAILABLE for %s" % chosen_dir)
+
+            try:
+                h = httplib.HTTP(sh, sp)
+                h.putrequest('GET', url)
+                h.endheaders()
+                errcode, errmsg, headers = h.getreply()
+                log_msg(2, "<-- HTTP-GET %s:%s %s: %s" % (sh, sp, url, str(errcode)))
+                h = h.file
+                desc = process_descriptors(h, "")
+            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)))
+
+        if len(seq) > MAXLEN:
+            fail = 1
+
+    if fail:
+        log_msg(2, "*** BLOSSOM: unreachable destination %s" % dest)
+        try:
+            conn.close_stream(streamID, 1)
+        except ErrorReply, e:
+            log_msg(1, e)
+
+    ## -- THESIS EXPERIMENT SECTION --
+
+    elif num_hops:
+        circID = 0
+        for i in range(1, num_hops):
+            chosen_dir = target_array[i % len(target_array)]
+
+            ff = 0
+            if circID:
+                ff = 1
+                semaphore[circID] = 0
+            circID = conn.extend_circuit(circID, [chosen_dir])
+
+            if ff:
+                start_time = time.time()
+                while(semaphore[circID] == 0):
+                    time.sleep(0.1)
+
+            if semaphore.has_key(circID) and semaphore[circID] == -1:
+                log_msg(1, "--- circuit failed")
+                del semaphore[circID]
+                break
+
+            if query_server:
+                if port.has_key(chosen_dir):
+                    chosen_server = "%s:%s" % (chosen_dir, port[chosen_dir])
+                    url = "http://%s.exit:%s/blossom/%s"; % (chosen_dir, port[chosen_dir], dest)
+                else:
+                    log_msg(1, "NO PORT AVAILABLE for %s" % chosen_dir)
+
+                try:
+                    h = httplib.HTTP(sh, sp)
+                    h.putrequest('GET', url)
+                    h.endheaders()
+                    errcode, errmsg, headers = h.getreply()
+                    log_msg(2, "<-- HTTP-GET %s:%s %s: %s" % (sh, sp, url, str(errcode)))
+                    h = h.file
+                    desc = process_descriptors(h, "")
+                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)))
+
+        if semaphore.has_key(circID):
+            conn.close_circuit(circID)
+            conn.close_stream(streamID, 1)
+
+    ## -- END THESIS EXPERIMENT SECTION --
+
+    else:
+        log_msg(2, "*** BLOSSOM: reachable destination %s" % dest)
+        try:
+            if explicit:
+                seq = [explicit]
+            else:
+                if path.has_key(dest):
+                    log_msg(2, "*** path[%s]: %s" % (dest, path[dest]))
+                    for rtr in path[dest]:
+                        if rtr != chosen_dir and rtr != NICK:
+                            seq.append(rtr)
+                seq.append(dest)
+
+            if not circID and len(seq) > 2:
+                log_msg(2, "*** EXTENDING for search")
+                c_from = seq[-2]
+                c_to = seq[-1]
+                for e_circID in circuits.keys():
+                    log_msg(2, "--- e_circID: %s %s" % (e_circID, circuits[e_circID][PATH]))
+                    if circuits[e_circID][PATH] and circuits[e_circID][PATH][-1] == c_from:
+                        log_msg(2, "--- choosing e_circID: %s" % e_circID)
+                        circID = e_circID
+                        seq = [dest]
+                        break
+                log_msg(2, "--- chosing seq: DONE")
+
+            # extend the circuit
+            log_msg(2, "*** final sequence for circ %s: %s" % (circID, repr(seq)))
+            circID = conn.extend_circuit(circID, seq)
+
+            # redirect stream if this is a first-time request to a new router
+            if re.match(r'^\.', target):
+                if desc.has_key(dest):
+                    line = desc[dest].split("\n")[0]
+                    log_msg(3, "--- line: %s" % line)
+                    m = re.match(r'^\S+\s+\S+\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s+', line)
+                    if m:
+                        conn.redirect_stream(streamID, m.group(1))
+
+            if circID:
+                if not pending_streams.has_key(circID):
+                    pending_streams[circID] = []
+                log_msg(2, "*** BLOSSOM: queueing %s for attachment to %s" % (streamID, circID))
+                pending_streams[circID].append(streamID)
+        except:
+            log_msg(1, "search unexpected: %s" % sys.exc_info()[0])
+
+    log_msg(2, "*** external processing complete for stream %s" % streamID)
+    return
+
+def process_line(line):
+    global circuits
+    global closed_streams
+    global counted_streams
+    global semaphore
+    global streams
+
+    # standard line format
+
+    line = re.sub(r" +", " ", line)
+    line = re.sub(r"\n", "", line)
+
+    item = line.split(" ")
+    code = item[0]
+    args = item[1:]
+
+    if code == "CIRC":
+        if len(args) == 3:
+            time, circID, status = args
+        elif len(args) == 4:
+            time, circID, status, c_path = args
+
+        if status == "LAUNCHED":
+            circuits[circID] = [[], 0, {}]
+
+        elif status == "EXTENDED":
+            circuits[circID] = [c_path.split(","), 0, {}]
+
+        elif status == "BUILT":
+            circuits[circID] = [c_path.split(","), 1, {}]
+
+        elif status == "FAILED" or status == "CLOSED":
+            if circuits.has_key(circID):
+                del circuits[circID]
+
+    elif code == "STREAM":
+        time, status, streamID, target, circID = args
+
+        if status == "NEW":
+            streams[streamID] = target
+
+        if status == "SENTCONNECT" or status == "SUCCEEDED":
+            if circuits.has_key(circID):
+                if not circuits[circID][STREAMS].has_key(streamID):
+                    for circ in circuits:
+                        if circuits[circ][STREAMS].has_key(streamID):
+                            del circuits[circ][STREAMS][streamID]
+                circuits[circID][STREAMS][streamID] = status
+
+        if status == "DETACHED":
+            if circuits.has_key(circID):
+                if circuits[circID][STREAMS].has_key(streamID):
+                    del circuits[circID][STREAMS][streamID]
+
+        if status == "CLOSED":
+            closed_streams[streamID] = int(time)
+
+        if status == "FAILED":
+            failed_streams[streamID] = int(time)
+
+    elif code in  ["DATA", "DATA+"]:
+        if len(args) > 3:
+            name = re.sub(r"^\*", "", args[1])
+            cc[name] = args[0].lower()
+            if code == "DATA":
+                tor_nodes[name] = 1
+            if re.match(r'[0-9]+', args[2]):
+                bw[name] = int(int(args[2])/1000)
+            else:
+                bw[name] = 0
+        if len(args) > 6:
+            addr[name] = args[4]
+            network[name] = args[5]
+            fingerprint[args[6]] = name
+
+    elif code == "POLICY":
+        if len(args) > 6:
+            name = re.sub(r"^\*", "", args[1])
+            qp = args[6]
+            if qp != "-" and int(args[2]) > 0:
+                if not policy.has_key(qp):
+                    policy[qp] = {}
+                log_msg(2, "--- SETTING POLICY: %s %s" % (qp, name))
+                policy[qp][name] = 1
+
+    elif code == "CC":
+        if len(args) > 1:
+            name = args[0]
+            cc_name[name] = " ".join(args[1:])
+
+    elif code == "CONSISTENCY":
+        check_consistency()
+
+    elif code == "FLUSH":
+        log_msg(2, "*** received FLUSH request")
+
+        circuits            = {}
+        closed_streams      = {}
+        counted_streams     = {}
+        streams             = {}
+
+    elif code == "EXIT":
+        raise BlossomError
+
+    return
+
+def signal_handler(sig, id):
+    if sig == signal.SIGHUP:
+        log_msg(1, "*** received signal HUP; restarting")
+        main()
+    elif sig == signal.SIGTERM:
+        log_msg(1, "*** received signal TERM; exiting")
+        sys.exit(0)
+    elif sig == signal.SIGUSR1:
+        f = 1
+        c = 0
+        output = ""
+        while(f):
+            c = c + 1
+            try:
+                f = sys._getframe(c)
+                output += " %s" % f.f_lineno
+            except ValueError:
+                f = []
+        if output:
+            output = "line%s" % output
+        else:
+            output = "no traceback"
+        log_msg(1, "*** received signal USR1: %s" % output)
+
+def getConnection(daemon=1):
+    """
+    getConnection tries to open a socket to the tor server.
+    If a socket is established, and the daemon paramter is True (the default),
+    a thread is spawned to handle the communcation between us and the tor server.
+    """
+    global NICK
+
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+    try:
+        host, port = parseHostAndPort(TORCONTROL)
+        s.connect((host,port))
+    except socket.error, e:
+        log_msg(1, "CONNECTION FAILED: %s. Is Tor running?  Is the ControlPort enabled?\n" % e)
+
+    try:
+        conn = get_connection(s)
+    except:
+        return
+    th = conn.launch_thread(daemon)
+
+    conn.authenticate("")
+    conn.set_event_handler(TorEventHandler())
+    conn.set_close_handler(TorCloseHandler)
+
+    k, v = conn.get_option("nickname")[0]
+    if v:
+        if NICK and NICK != v:
+            log_msg(1, "Tor nickname has changed.  Please restart Blossom.")
+            raise BlossomError
+        NICK = v
+    else:
+        NICK = ""
+
+    if BLOSSOM:
+        conn.set_option("__leavestreamsunattached", "1")
+    else:
+        conn.set_option("__leavestreamsunattached", "0")
+
+    conn.set_events(["CIRCSTATUS", "STREAMSTATUS"])
+
+    return conn
+
+def get_info(name):
+    global conn
+
+    try:
+        r = conn.get_info(name)
+        entries = r[name].split("\r\n")
+        n = 0
+        try:
+            while 1:
+                entries.remove("")
+        except:
+            pass
+        return entries
+    except AttributeError, e:
+        log_msg(1, "AttributeError %s" % repr(e))
+        conn = getConnection()
+    except TorCtlClosed:
+        log_msg(1, "*** retrying controller connection [%s]" % get_curr_time())
+        conn = getConnection()
+    time.sleep(TIME_RETRY)
+    return get_info(name)
+
+def check_consistency():
+    global circuits
+    global closed_streams
+
+    err = 0
+
+    sh, sp = parseHostAndPort(TORCONTROL)
+
+    log_msg(2, "verifying consistency")
+    counted = {}
+    for ent in get_info("circuit-status"):
+        circID, status, path = ent.split(" ")
+        if circuits.has_key(circID):
+            log_msg(3, "circID %s OK" % circID)
+            counted[circID] = 1
+        else:
+            log_msg(1, "circID %s in Tor but not our records (INCONSISTENCY)" % circID)
+            err = 1
+    for circID in circuits.keys():
+        if not counted.has_key(circID):
+            log_msg(1, "circID %s in our records but not Tor (INCONSISTENCY)" % circID)
+            err = 1
+
+    counted = {}
+    for ent in get_info("stream-status"):
+        t = ent.split(" ")
+        if len(t) == 3:
+            streamID, status, target = t
+        elif len(t) == 4:
+            streamID, status, target, circID = t
+        else:
+            log_msg(1, "CONSISTENCY: ill-formed stream-status record")
+            err = 1
+        if streams.has_key(streamID):
+            log_msg(2, "streamID %s OK" % streamID)
+            counted[streamID] = 1
+        else:
+            log_msg(1, "streamID %s in Tor but not our records (INCONSISTENCY)" % streamID)
+            err = 1
+    for streamID in streams.keys():
+        if not counted.has_key(streamID) and not closed_streams.has_key(streamID):
+            log_msg(1, "streamID %s in our records but not Tor (INCONSISTENCY)" % streamID)
+            err = 1
+
+    if err:
+        log_msg(1, "*** CONSISTENCY CHECK FAILED")
+    else:
+        log_msg(2, "*** CONSISTENCY CHECK SUCCEEDED")
+
+    return err
+
+# process descriptors from a file and export them to the Tor client
+def process_descriptors(h, remote):
+    global INIT
+    global closed_streams
+    global conn
+    global summary_remote
+
+    desc = {}
+    record = ""
+    router = ""
+
+    log_msg(3, "*** parsing descriptors")
+
+    while 1:
+        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 == "\n":
+            if router:
+                desc[router] = record
+                if (not BLOSSOM) or (BLOSSOM and BLOSSOM.__contains__(remote)):
+                    local[router] = 1
+            record = ""
+            router = ""
+
+        else:
+
+            # unsigned-directory
+            m = re.search(r'^unsigned-directory\s+(\S+)\s*(\s+(.*))?$', line)
+            if m:
+                persist_nickname[remote] = m.group(1)
+                INIT = 1
+
+            # blossom-path
+            m = re.search(r'^blossom-path\s+(\S+)\s*(\s+(\S+))?$', line)
+            if m:
+                btarget = m.group(1)
+
+                # delete path if one exists
+                if path.has_key(btarget):
+                    del path[btarget]
+
+                # set a new path if one is available
+                if m.group(3):
+                    bpath = m.group(3)
+
+                    # enforce well-formedness of blossom-path
+                    if re.match(r'^([0-9A-Za-z]+\,)*[0-9A-Za-z]+$', bpath):
+                        log_msg(3, "setting path: %s %s" % (btarget, bpath))
+                        path[btarget] = bpath.split(",")
+                    else:
+                        log_msg(2, "invalid path specification: %s %s" % (btarget, bpath))
+
+                continue
+
+            # metadata
+            m = re.search(r'^metadata\s+(\S+)\s+(\S+)$', line)
+            if m:
+                btarget = m.group(1)
+                bmeta = m.group(2)
+
+                if re.match(r'^[0-9a-z,-.]+$', bmeta):
+                    log_msg(3, "setting metadata: %s %s" % (btarget, bmeta))
+                    metadata[btarget] = bmeta.split(",")
+
+                continue
+
+            # router
+            m = re.search(r'^router\s+(\S+)\s+', line)
+            if m:
+
+                # determine router nickname
+                router = m.group(1).lower()
+
+            # summary
+            m = re.search(r'^summary\s+(\S+)\s*(\s+(\S+))?$', line)
+            if m:
+
+                # determine descriptors provided by this directory
+                dir = m.group(1)
+                rtr_list = []
+                if m.group(3):
+                    rtr_list = m.group(3).split(",")
+                if remote:
+                    summary[dir] = {}
+                else:
+                    summary_remote[dir] = {}
+                for rtr_entry in rtr_list:
+                    if re.search(r'=', rtr_entry):
+                        rtr_entry = rtr_entry.split("=")
+                        rtr = rtr_entry[0].lower()
+                        rtr_dist = int(rtr_entry[1])
+                    else:
+                        rtr = rtr_entry
+                        rtr_dist = 1
+                    if remote:
+                        summary[dir][rtr] = rtr_dist
+                    else:
+                        summary_remote[dir][rtr] = rtr_dist
+
+            # compiled-metadata
+            m = re.search(r'^compiled-metadata\s+(\S+)\s*(\s+(\S+))?$', line)
+            if m:
+
+                # determine metadata provided by this directory
+                dir = m.group(1)
+                metadata_list = []
+                if m.group(3):
+                    metadata_list = m.group(3).split(",")
+                dir_metadata[dir] = {}
+                for metadata_entry in metadata_list:
+                    dir_metadata[dir][metadata_entry] = 1
+
+            # directory
+            m = re.search(r'^directory\s+(\S+)\s+(\S+)\s*(\s+(\S+))?$', line)
+            if m:
+
+                # determine directory port and propagation information
+                dir = m.group(1)
+                port[dir] = m.group(2)
+
+                if remote:
+                    if m.group(4):
+                        prop[dir] = m.group(4).split(",")
+                    else:
+                        prop[dir] = []
+
+            record += line
+
+    # post descriptors
+
+    for router in desc.keys():
+        log_msg(3, "posting descriptor: " + router)
+        try:
+            conn.post_descriptor(desc[router])
+        except AttributeError, e:
+            log_msg(1, "post descriptor failed: %s" % e)
+            conn = getConnection()
+        except ErrorReply, e:
+            log_msg(1, e)
+
+    log_msg(3, "*** parsing descriptors COMPLETED")
+    return desc
+
+# execute a routine directory fetch
+def get_descriptors(b):
+    dh = "0.0.0.0"
+    dp = 80
+    url = "http://%s/tor/"; % b
+
+    log_msg(2, "get_descriptors: %s" % url)
+    m = re.search(r'^([^/:]+)(:[0-9]+)?$', b)
+    if m:
+        dh = m.group(1)
+        if m.group(2):
+            dp = int(m.group(2)[1:])
+        url = "/tor/"
+
+    log_msg(2, "<-- HTTP-GET %s:%s %s" % (dh, dp, url))
+    thread = OpenURLThread(dh, dp, url)
+    thread.setDaemon(1)
+    thread.start()
+    return
+
+def obtain_tor_metadata(url, policy=0):
+    log_msg(2, "*** attempting to retrieve Tor metadata (%s)" % policy)
+    dh = "0.0.0.0"
+    dp = 80
+
+    if HTTP_PROXY:
+        dh, dp = parseHostAndPort(HTTP_PROXY)
+    else:
+        m = re.search(r'^http:\/\/([^/:]+)(:[0-9]+)?(\/.*)$', url)
+        if m:
+            dh = m.group(1)
+            if m.group(2):
+                dp = int(m.group(2)[1:])
+            url = m.group(3)
+
+    log_msg(2, "<-- HTTP-GET %s:%s %s" % (dh, dp, url))
+    thread = OpenURLThread(dh, dp, url, policy=policy)
+    thread.setDaemon(1)
+    thread.start()
+    return
+
+def get_tor_policy(qp):
+    log_msg(3, "*** get_tor_policy for port %s" % qp)
+
+    urllist = [POLICY_URL]
+    if BLOSSOM:
+        urllist.append(POLICY_URL_BLOSSOM)
+    if not BLOSSOM_ARGS:
+        urllist = []
+
+    if not policy_time.has_key(qp) or policy_time[qp] + TIME_POLICY < time.time():
+        for url in urllist:
+            p_url = "%s=%s" % (url, qp)
+            log_msg(2, "*** get_tor_policy: %s" % p_url)
+            obtain_tor_metadata(p_url, policy=1)
+        policy_time[qp] = time.time()
+
+    log_msg(3, "*** get_tor_policy for port %s SUCCEEDED" % qp)
+
+def update_path(router, new_path, override):
+    global queue
+
+    # delete blossom-path and blossom-addr entries if applicable
+    if dir_path.has_key(router):
+        log_msg(3, "*** COMPARING (%s) path length %s <-> %s [%s <-> %s]" \
+            % (override, len(new_path), len(dir_path[router]), repr(new_path), repr(dir_path[router])))
+        if len(new_path) >= len(dir_path[router]) and not override:
+            return 0
+        del dir_path[router]
+
+    # create new blossom-path entry for this router, if applicable
+    log_msg(3, "*** SETTING PATH for %s: %s" % (router, repr(new_path)))
+    queue += "blossom-path %s %s\n" % (router, ",".join(new_path))
+    if new_path:
+        dir_path[router] = new_path
+
+    return 1
+
+def parse_queue(queue):
+    lines = queue.split("\n")
+    entries = {}
+    processing_router = 0
+
+    for line in lines:
+        if processing_router:
+            if line == "":
+                entries[router]["router"] = new_desc
+                processing_router = 0
+            else:
+                new_desc += "%s\n" % line
+        else:
+            if len(line) == 0:
+                continue
+
+            m = re.match(r'^router\s+(\S+)\s+\S+', line)
+            if m:
+                processing_router = 1
+                router = m.group(1).lower()
+                if not entries.has_key(router):
+                    entries[router] = {}
+                new_desc = "%s\n" % line
+                continue
+
+            m = re.match(r'^(\S+)\s+(\S+)\s*(\s+.*)?$', line)
+            if m:
+                entry_type = m.group(1)
+                router = m.group(2)
+                if not entries.has_key(router):
+                    entries[router] = {}
+                entries[router][entry_type] = line
+                continue
+
+            if re.match(r'^\r$', line):
+                continue
+            if re.match(r'^end$', line):
+                continue
+
+            log_msg(1, "queue parse error: %s" % line)
+
+    return entries
+
+def parse_blossom(lines, peer):
+    global metadata_pending
+    global queue
+    global summary_pending
+
+    pending = {}
+
+    new_path = []
+    new_meta = []
+
+    processing_router = 0
+    if len(lines) == 0:
+        return
+    for line in lines:
+        if processing_router:
+            new_desc += "%s\n" % line
+            m = re.match(r'opt\s+fingerprint\s+(\S+.*)$', line)
+            if m:
+                dir_fingerprint[router] = re.sub(" ", "", m.group(1))
+            if line == "-----END SIGNATURE-----":
+                f = 1
+            if line == "":
+                processing_router = 0
+
+                # publish if we have the entire descriptor
+                if f == 1:
+                    log_msg(2, "--- VALID: %s" % router)
+                    log_msg(3, "--- dir_summary: %s" % repr(dir_summary))
+
+                    if peer:
+                        if (neighbors_recv[peer] == "summarize" \
+                                or neighbors_recv[peer] == "proxy") \
+                                and not dir_summary.has_key(router) \
+                                and not peer == router:
+                            update_time[router] = time.time()
+                        else:
+                            if not pending.has_key(router):
+                                pending[router] = ["", "", ""]
+                            pending[router][DESC] = new_desc
+                    else:
+                        update_time[router] = time.time()
+                        desc[router] = new_desc
+                        queue += new_desc
+                        queue += "router-advertisement %s\n" % router
+
+        else:
+            if len(line) == 0:
+                continue
+
+            log_msg(3, "--- line: %s" % line)
+
+            # router
+            m = re.match(r'^router\s+(\S+)\s+\S+', line)
+            if m:
+                processing_router = 1
+                router = m.group(1).lower()
+                new_desc = "%s\n" % line
+                continue
+
+            # summary
+            m = re.match(r'^summary\s+(\S+)\s*(\s+(\S+))?$', line)
+            if m and peer:
+                dir = m.group(1)
+                rtr_list = []
+                if m.group(3):
+                    rtr_list = m.group(3).split(",")
+                summary_pending[dir] = rtr_list
+                log_msg(3, "--- received summary from %s: %s" % (peer, line))
+                continue
+
+            # compiled-metadata
+            m = re.match(r'^compiled-metadata\s+(\S+)\s*(\s+(\S+))?$', line)
+            if m and peer:
+                dir = m.group(1)
+                metadata_list = []
+                if m.group(3):
+                    metadata_list = m.group(3).split(",")
+                metadata_pending[dir] = metadata_list
+                log_msg(3, "--- received compiled-metadata from %s: %s" % (peer, line))
+                continue
+
+            # router-advertisement
+            m = re.match(r'^router-advertisement\s+(\S+)(\s+(\S+))?$', line)
+            if m and peer:
+                router = m.group(1).lower()
+
+                if pending.has_key(router):
+                    f = 1
+
+                    if peer and neighbors_recv[peer] == "proxy":
+                        if not dir_proxy.has_key(peer):
+                            dir_proxy[peer] = {}
+                        if not dir_proxy[peer].has_key(NICK):
+                            dir_proxy[peer][NICK] = {}
+                        dir_proxy[peer][NICK][router] = 1
+                        if received_path.has_key(peer) and received_path[peer].has_key(router):
+                            dir_proxy[peer][NICK][router] += len(received_path[peer][router])
+
+                    log_msg(3, "--- received router-advertisement from %s: %s" % (peer, line))
+
+                    new_adv = []
+                    if m.group(3):
+                        new_adv = m.group(3).split(",")
+
+                    for index in range(len(new_adv)):
+                        if new_adv[index] == NICK:
+                            if index > 1:
+                                new_adv = new_adv[:index-1]
+                            else:
+                                new_adv = []
+                            log_msg(3, "*** BREAKING CYCLE at position %s: %s" % (index, new_adv))
+                            f = 0
+                            break
+                    new_adv.append(peer)
+
+                    # perform router summaries
+
+                    if (peer != router)                                 \
+                            and (neighbors_recv[peer] == "summarize"\
+                            or neighbors_recv[peer] == "proxy"):
+
+                        # generate summary
+
+                        if not dir_summary.has_key(peer):
+                            dir_summary[peer] = {}
+                        if not dir_summary[peer].has_key(router):
+                            if router != NICK and router != peer:
+                                dir_summary[peer][router] = 1
+                                if received_path.has_key(peer) \
+                                        and received_path[peer].has_key(router):
+                                    dir_summary[peer][router] += len(received_path[peer][router])
+                            rtr_list = []
+                            for rtr in dir_summary[peer].keys():
+                                rtr_list.append("%s=%s" % (rtr, dir_summary[peer][rtr]))
+                            rtr_list.sort()
+                            queue += "summary %s %s\n" \
+                                % (peer, ",".join(rtr_list))
+                            queue += "directory %s %s %s\n" \
+                                % (peer, dir_port[peer], ",".join(dir_prop[peer][peer]))
+                        if not dir_summary.has_key(router):
+                            f = 0
+
+                        # generate compiled-metadata
+
+                        mf = 0
+                        if not dir_metadata.has_key(peer):
+                            dir_metadata[peer] = {}
+                            mf = 1
+                        if metadata.has_key(router):
+                            for md in metadata[router]:
+                                if not dir_metadata[peer].has_key(md):
+                                    dir_metadata[peer][md] = 1
+                                    mf = 1
+                        if not mf:
+                            all_metadata = {}
+                            for md_source in metadata.keys():
+                                for md in metadata[md_source]:
+                                    all_metadata[md] = 1
+                            for md in dir_metadata[peer]:
+                                if not all_metadata.has_key(md):
+                                    mf = 1
+                            dir_metadata[peer] = all_metadata
+                        if mf:
+                            queue += "compiled-metadata %s %s\n" \
+                                % (peer, ",".join(dir_metadata[peer]))
+
+                    if f:
+                        new_line = "router-advertisement %s %s" % (router, ",".join(new_adv))
+                        override = 0
+                        if router_adv.has_key(router):
+                            log_msg(3, "--- router_adv [for override]: %s" \
+                                % repr(router_adv[router]))
+                            if len(router_adv[router]) and router_adv[router][-1] == peer:
+                                override = 2
+                        if update_path(router, pending[router][PATH], override):
+                            router_adv[router] = new_adv
+                            update_time[router] = time.time()
+                            desc[router] = pending[router][DESC]
+
+                            queue += desc[router]
+
+                            metadata[router] = pending[router][META]
+                            if metadata[router]:
+                                log_msg(3, "--- metadata[%s]: %s" \
+                                    % (router, ",".join(metadata[router])))
+                                new_metadata = "metadata %s %s\n" \
+                                    % (router, ",".join(metadata[router]))
+                                queue += new_metadata
+
+                            queue += "%s\n" % new_line
+
+                    log_msg(3, "--- desc.keys(): %s" % desc.keys())
+
+                continue
+
+            # blossom-path
+            m = re.match(r'^blossom-path\s+(\S+)\s*(\s+(\S+))?$', line)
+            if m:
+                router = m.group(1).lower()
+                new_path = []
+
+                if m.group(3):
+
+                    # enforce well-formedness of blossom-path
+                    bpath = m.group(3)
+                    if re.match(r'^([0-9A-Za-z]+\,)*[0-9A-Za-z]+$', bpath):
+                        log_msg(3, "setting path: %s %s" % (router, bpath))
+                        new_path = bpath.split(",")
+                    else:
+                        log_msg(2, "invalid path specification: %s %s" % (router, bpath))
+
+                if peer:
+                    if not pending.has_key(router):
+                        pending[router] = ["", "", ""]
+                    if neighbors_recv[peer] == "prepend":
+                        if new_path:
+                            pending[router][PATH] = [NICK]
+                            pending[router][PATH].extend(new_path)
+                        else:
+                            pending[router][PATH] = [NICK]
+                    else:
+                        pending[router][PATH] = new_path
+                    if not received_path.has_key(peer):
+                        received_path[peer] = {}
+                    received_path[peer][router] = new_path
+                    log_msg(3, "*** setting received_path[%s][%s] = %s" \
+                        % (peer, router, received_path[peer][router]))
+                else:
+                    update_path(router, new_path, 1)
+                continue
+
+            # metadata
+            m = re.match(r'^metadata\s+(\S+)\s+(\S+)$', line)
+            if m:
+                router = m.group(1).lower()
+                bmeta = m.group(2).lower()
+                if re.match(r'^[0-9a-z,-.]+$', bmeta):
+                    log_msg(3, "setting metadata: %s %s" % (router, bmeta))
+                    if peer:
+                        pending[router][META] = bmeta.split(",")
+                    else:
+                        metadata[router] = bmeta.split(",")
+                        new_metadata = "metadata %s %s\n" \
+                            % (router, ",".join(metadata[router]))
+                        queue += new_metadata
+                else:
+                    log_msg(2, "invalid metadata specification: %s %s" % (router, bmeta))
+                continue
+
+            # directory
+            m = re.match(r'^directory\s+(\S+)\s+(\S+)\s*(\s+(\S+))?$', line)
+            if m and peer:
+                f = 1
+                dir = m.group(1)
+                port = m.group(2)
+
+                if m.group(4):
+                    prop = m.group(4).split(",")
+                else:
+                    prop = []
+
+                if dir == NICK:
+                    prop = []
+                    f = 0
+
+                if selection.has_key(dir) and dir_prop[selection[dir]].has_key(dir):
+                    if len(dir_prop[selection[dir]][dir]) < len(prop) + 1:
+                        f = 0
+
+                if not dir_prop.has_key(peer):
+                    dir_prop[peer] = {}
+
+                prop.append(peer)
+
+                if prop.__contains__(NICK):
+                    f = 0
+
+                if f:
+                    dir_prop[peer][dir] = prop
+                    log_msg(3, "--- dir_prop %s" % repr(dir_prop))
+                    selection[dir] = peer
+
+                    dir_port[dir] = port
+
+                    if summary_pending.has_key(dir):
+                        log_msg(3, "--- summary_pending[%s]: %s" \
+                            % (dir, summary_pending[dir]))
+                        if neighbors_recv[peer] == "proxy":
+                            log_msg(3, "--- peer: %s" % peer)
+                            log_msg(3, "--- dir_proxy: %s" % repr(dir_proxy))
+                            log_msg(3, "--- summary_pending: %s" % repr(summary_pending))
+                            dir_summary[peer] = {}
+                            if not dir_proxy.has_key(peer):
+                                dir_proxy[peer] = {}
+                            dir_proxy[peer][dir] = {}
+                            for rtr_entry in summary_pending[dir]:
+                                if re.search(r'=', rtr_entry):
+                                    rtr_entry = rtr_entry.split("=")
+                                    rtr = rtr_entry[0].lower()
+                                    rtr_dist = int(rtr_entry[1])
+                                else:
+                                    rtr = rtr_entry
+                                    rtr_dist = 1
+                                log_msg(3, "--- dir_prop[%s][%s]: %s" \
+                                    % (peer, dir, dir_prop[peer][dir]))
+                                dir_proxy[peer][dir][rtr] = rtr_dist + len(dir_prop[peer][dir])
+                                if new_path and dir_proxy[peer][dir].has_key(rtr):
+                                    dir_proxy[peer][dir][rtr] += len(new_path)
+                                log_msg(3, "--- dir_proxy[%s][%s][%s]: %s" \
+                                    % (peer, dir, rtr, dir_proxy[peer][dir][rtr]))
+                            for s_dir in dir_proxy[peer]:
+                                log_msg(3, "--- dir_proxy[%s][%s]: %s" \
+                                    % (peer, s_dir, repr(dir_proxy[peer][s_dir])))
+                                for rtr in dir_proxy[peer][s_dir]:
+                                    if rtr != NICK                                      \
+                                            and ((not dir_summary[peer].has_key(rtr))   \
+                                            or (dir_summary[peer].has_key(rtr)          \
+                                            and dir_summary[peer][rtr] > dir_proxy[peer][s_dir][rtr])):
+                                        dir_summary[peer][rtr] = dir_proxy[peer][s_dir][rtr]
+                                        log_msg(3, "--- setting directory dir_summary[%s][%s]: %s" \
+                                            % (peer, rtr, repr(dir_summary[peer])))
+                            queue += "summary %s %s\n" \
+                                % (peer, ",".join(dir_summary[peer].keys()))
+                        else:
+                            dir_summary[dir] = {}
+                            for rtr_entry in summary_pending[dir]:
+                                if re.search(r'=', rtr_entry):
+                                    rtr_entry = rtr_entry.split("=")
+                                    rtr = rtr_entry[0].lower()
+                                    rtr_dist = int(rtr_entry[1])
+                                else:
+                                    rtr = rtr_entry
+                                    rtr_dist = 1
+                                dir_summary[dir][rtr] = rtr_dist
+                                if new_path:
+                                    dir_summary[dir][rtr] += len(new_path)
+                            queue += "summary %s %s\n" \
+                                % (dir, ",".join(dir_summary[dir].keys()))
+
+                    if metadata_pending.has_key(dir):
+                        log_msg(3, "--- metadata_pending[%s]: %s" \
+                            % (dir, metadata_pending[dir]))
+                        if neighbors_recv[peer] == "proxy":
+                            dir_metadata[peer] = {}
+                            for metadata_entry in metadata_pending[dir]:
+                                dir_metadata[peer][metadata_entry] = 1
+                            queue += "compiled-metadata %s %s\n" \
+                                % (peer, ",".join(dir_metadata[peer].keys()))
+                        else:
+                            dir_metadata[dir] = {}
+                            for metadata_entry in metadata_pending[dir]:
+                                dir_metadata[dir][metadata_entry] = 1
+                            queue += "compiled-metadata %s %s\n" \
+                                % (dir, ",".join(dir_metadata[dir].keys()))
+
+                    queue += "directory %s %s %s\n" \
+                        % (dir, dir_port[dir], ",".join(dir_prop[peer][dir]))
+
+                summary_pending = {}
+                metadata_pending = {}
+                continue
+
+            # withdraw
+
+            m = re.match(r'^withdraw\s+(\S+)\s*(\s+(\S+))?$', line)
+            if m and peer:
+                dir = m.group(1)
+
+                if selection.has_key(dir):
+                    if dir_prop.has_key(peer) and dir_prop[peer].has_key(dir):
+                        del dir_prop[peer][dir]
+                        if m.group(3):
+                            dir_prop[peer][dir] = m.group(3).split(",")
+                    if selection[dir] == peer:
+                        alt = ""
+                        select_optimal(dir)
+                        if selection.has_key(dir):
+                            alt = ",".join(dir_prop[selection[dir]][dir])
+                        queue += "withdraw %s %s\n" % (dir, alt)
+
+                continue
+
+            log_msg(1, "BLOSSOM: parse error: %s" % line)
+
+    log_msg(3, "*** parse_blossom done")
+    return
+
+def parse_update(lines):
+    global queue
+
+    log_msg(3, "*** parse_update: %s" % lines[0])
+
+    m = re.match(r'^directory-update\s+(\S+)\s+(\S+)$', lines[0])
+    if m:
+        peer = m.group(1)
+        dir_port[peer] = m.group(2)
+
+        log_msg(3, "*** %s" % lines[0])
+
+        full[peer] = time.time()
+        selection[peer] = peer
+
+        if not dir_prop.has_key(peer):
+            dir_prop[peer] = {}
+
+        dir_prop[peer][peer] = []
+
+        queue += "directory %s %s\n" % (peer, dir_port[peer])
+
+        if not neighbors_recv.has_key(peer) or neighbors_recv[peer] == "none":
+            log_msg(3, "--- peer: %s" % peer)
+            log_msg(3, "--- neighbors_recv: %s" % repr(neighbors_recv))
+            log_msg(2, "<-- %s REJECTED" % lines[0])
+            return
+
+        peer = m.group(1)
+        log_msg(3, "<-- " + lines[0])
+
+        pending = {}
+        parse_blossom(lines[1:], peer)
+        log_msg(3, "<-- %s COMPLETED" % lines[0])
+
+def parse_configuration(conflines):
+    global DIR_SERVER
+
+    for index in range(len(conflines)):
+        m = re.match(r'^directory\s+(\S+)\s*$', conflines[index])
+        if m:
+            DIR_SERVER = m.group(1)
+
+        m = re.match(r'^neighbor\s+(\S+)\s+(\S+)\s+(\S+)\s*$', conflines[index])
+        if m:
+            opt_name = m.group(1)
+            opt_recv = m.group(2)
+            opt_send = m.group(3)
+
+            neighbors_recv[opt_name] = opt_recv
+            if opt_send != "-":
+                neighbors_send[opt_name] = parseHostAndPort(opt_send)
+
+            log_msg(2, conflines[index])
+
+def select_optimal(dir):
+    log_msg(3, "DIR select_optimal: %s" % repr(dir_prop))
+
+    min_len = MAXINT
+    new_selection = ""
+
+    for peer in dir_prop.keys():
+        if dir_prop[peer].has_key(dir):
+            opt_path = dir_prop[peer][dir]
+            if len(opt_path) < min_len:
+                min_len = len(opt_path)
+                new_selection = peer
+
+    if new_selection:
+        selection[dir] = new_selection
+    else:
+        if selection.has_key(dir):
+            del selection[dir]
+
+    log_msg(2, "DIR selection for %s: %s" % (dir, new_selection))
+
+    return new_selection
+
+def reap_disconnected_neighbors(curr_time):
+    global queue
+
+    for dir in full.keys():
+        if curr_time - full[dir] > DIR_PEER_KEEPALIVE:
+            alt = ""
+            del full[dir]
+            if dir_prop.has_key(dir) and dir_prop[dir].has_key(dir):
+                del dir_prop[dir][dir]
+
+            select_optimal(dir)
+            if selection.has_key(dir):
+                alt = ",".join(dir_prop[selection[dir]][dir])
+
+            queue += "withdraw %s %s\n" % (dir, alt)
+
+def handle_callbacks_individually(httpd, processing_dir=0):
+    global interesting_ports
+    global queue
+
+    queue = ""
+    DIR_REG_LOCK = 0
+
+    if processing_dir:
+        last = 0
+    else:
+        initialize()
+        last = [0, 0, 0, 0, 0]
+        log_msg(2, "*** entering main loop")
+
+    while 1:
+        try:
+            (r, w, o) = select.select([httpd], [], [], TIME_RETRY)
+            if r:
+                request, client_address = httpd.get_request()
+                if httpd.verify_request(request, client_address):
+                    try:
+                        httpd.process_request(request, client_address)
+                    except SystemExit:
+                        sys.exit(0)
+                    except KeyboardInterrupt:
+                        log_msg(1, "exiting on ^C [%s]\n" % get_curr_time())
+                    except:
+                        log_msg(1, "secondary unexpected: %s" % sys.exc_info()[0])
+                        httpd.handle_error(request, client_address)
+                        httpd.close_request(request)
+            if processing_dir and time.time() - last > DIR_POLL_INTERVAL and not DIR_REG_LOCK:
+                last = time.time()
+                thread = PeriodicDirectoryThread(queue)
+                thread.setDaemon(1)
+                thread.start()
+                queue = ""
+            if not processing_dir and time.time() - last[T_PERIODIC] > TIME_RETRY:
+                last[T_PERIODIC] = time.time()
+                thread = PeriodicClientThread(last, interesting_ports)
+                interesting_ports = []
+                thread.setDaemon(1)
+                thread.start()
+        except select.error, (ec, em):
+            log_msg(1, "select.error %s %s" % (ec, em))
+        except socket.error:
+            log_msg(1, "socket.error (aborting): %s" % sys.exc_info()[0])
+            return
+
+def send_updates(queue):
+    log_msg(3, "*** send_updates: queue length %s" % len(queue.split("\n")))
+    entries = parse_queue(queue)
+    msg = "directory-update %s %s\n\n" % (NICK, DIR_PORT)
+
+    for node in entries.keys():
+        tokens = entries[node]
+
+        if tokens.has_key("withdraw"):
+            log_msg(3, "%s" % tokens["withdraw"])
+            msg += "%s\n" % tokens["withdraw"]
+            del tokens["withdraw"]
+
+        if tokens.has_key("summary"):
+            log_msg(3, "%s" % tokens["summary"])
+            msg += "%s\n" % tokens["summary"]
+            del tokens["summary"]
+
+        if tokens.has_key("compiled-metadata"):
+            log_msg(2, "%s" % tokens["compiled-metadata"])
+            msg += "%s\n" % tokens["compiled-metadata"]
+            del tokens["compiled-metadata"]
+
+        if tokens.has_key("directory"):
+            if len(tokens.keys()) > 1:
+                log_msg(2, "%s" % tokens["directory"])
+                msg += "%s\n" % tokens["directory"]
+            else:
+                log_msg(3, "*** superfluous: %s" % tokens["directory"])
+            del tokens["directory"]
+
+        if tokens.has_key("router")                         \
+                and tokens.has_key("router-advertisement")  \
+                and tokens.has_key("blossom-path"):
+            log_msg(3, "router %s" % node)
+
+            msg += "\n%s\n" % tokens["router"]
+            del tokens["router"]
+
+            msg += "%s\n" % tokens["blossom-path"]
+            del tokens["blossom-path"]
+
+            if tokens.has_key("metadata"):
+                log_msg(3, "%s" % tokens["metadata"])
+                msg += "%s\n" % tokens["metadata"]
+                del tokens["metadata"]
+
+            for field in tokens.keys():
+                if field != "router"                    \
+                        and field != "router-advertisement" \
+                        and field != "metadata"         \
+                        and field != "blossom-path":
+                    msg += "%s\n" % tokens[field]
+                    log_msg(2, "-*- %s\n" % tokens[field])
+                    del tokens[field]
+
+            msg += "%s\n" % tokens["router-advertisement"]
+
+    for peer in neighbors_send.keys():
+        (dh, dp) = neighbors_send[peer]
+        log_msg(3, "--> update %s:%s" % (dh, dp))
+        thread = HTTPPostThread("%s:%s" % (dh, dp), "/blossom/directory-update", msg)
+        thread.setDaemon(1)
+        thread.start()
+
+    queue = ""
+
+def initialize():
+    global conn
+    global unestablished
+
+    curr_time = int(time.time())
+    interesting_ports = []
+
+    unestablished = {}
+    for elt in BLOSSOM:
+        unestablished[elt] = 1
+
+    try:
+        conn.close()
+    except:
+        pass
+
+    while 1:
+        try:
+            conn = getConnection()
+            log_msg(2, "*** OPENING CONTROLLER CONNECTION: initialize")
+            break
+        except socket.error, e:
+            err_code, err_msg = e
+            log_msg(1, "getConnection socket.error %s %s" % (repr(err_code), repr(err_msg)))
+            time.sleep(TIME_RETRY)
+        except IOError, (ec, em):
+            log_msg(1, "getConnection IOError: %s" % em)
+            raise BlossomError
+
+    for ent in get_info("circuit-status"):
+        text = "CIRC %s %s\n" % (curr_time, ent)
+        process_line(text)
+
+    for ent in get_info("stream-status"):
+        text = "STREAM %s %s\n" % (curr_time, ent)
+        process_line(text)
+
+    # parse the country codes file
+
+    log_msg(3, "*** parsing country codes file")
+
+    try:
+        f = open(F_COUNTRY_CODES)
+    except IOError, (errno, strerror):
+        log_msg(0, "cannot read country codes file: %s\n" % strerror)
+        raise BlossomError
+
+    while 1:
+        try:
+            line = f.readline()
+        except:
+            break
+        if not line:
+            break
+        m = re.match(r"^(\S\S)\s+(\S.*)$", line)
+        if m and m.group(1) and m.group(2):
+            cc_name[m.group(1).lower()] = m.group(2)
+
+    c = 0
+    for name in cc_name.keys():
+        c += 1
+        process_line("CC %s %s\n" % (name, cc_name[name]))
+
+def main():
+    # declare configuration variables
+
+    global AUTOREFRESH
+    global BLOSSOM
+    global BLOSSOM_ARGS
+    global CONFFILE
+    global CONNECTION_CLOSED
+    global DEBUG
+    global DIR_POLL_INTERVAL
+    global DIR_PORT
+    global DIR_REG_LOCK
+    global DIR_SERVER
+    global DISCLOSE_TARGET
+    global ENABLE_DIR
+    global F_COUNTRY_CODES
+    global F_ROOT
+    global HTTP_PROXY
+    global INIT
+    global LOGLEVEL
+    global META_LOCAL
+    global NICK
+    global PERSIST
+    global POLICY_URL
+    global POLICY_URL_BLOSSOM
+    global SERVER
+    global STATUS_URL
+    global STATUS_URL_BLOSSOM
+    global TORCONTROL
+    global WEB_STATUS
+
+    # declare other global variables
+
+    global addr
+    global attempted
+    global circuits
+    global closed_streams
+    global conn
+    global counted_streams
+    global dir_metadata
+    global failed_streams
+    global fingerprint
+    global interesting_ports
+    global local
+    global metadata
+    global metadata_pending
+    global neighbors_send
+    global neighbors_recv
+    global network
+    global path
+    global pending_streams
+    global persist_id
+    global persist_nickname
+    global policy
+    global policy_time
+    global port
+    global prop
+    global query_streams
+    global queue
+    global semaphore
+    global streams
+    global summary
+    global summary_remote
+    global threads
+    global tor_nodes
+    global unestablished
+    global update_time
+
+    # declare local variables
+
+    dir_httpd   = []
+    dh          = ""
+    opts        = []
+    pargs       = []
+    usage       = 0
+
+    try:
+        opts, pargs = getopt.getopt(sys.argv[1:], "b:c:d:f:gi:m:p:r:s:t:vw:xy")
+    except getopt.GetoptError, e:
+        usage = 1
+    if pargs:
+        usage = 1
+
+    # parse command-line options
+
+    for o, a in opts:
+        if o == "-b":
+            if ENABLE_DIR:
+                log_msg(0, "cannot specify both -b and -f options")
+                sys.exit(0)
+            else:
+                BLOSSOM = a.split(",")
+        if o == "-c":
+            TORCONTROL = a
+        if o == "-d":
+            DEBUG = int(a)
+        if o == "-f":
+            if BLOSSOM:
+                log_msg(0, "cannot specify both -b and -f options")
+                sys.exit(0)
+            else:
+                CONFFILE = a
+                ENABLE_DIR = 1
+                BLOSSOM = [DIR_SERVER]
+        if o == "-g":
+            DISCLOSE_TARGET = 0
+        if o == "-i":
+            DIR_POLL_INTERVAL = int(a)
+        if o == "-m":
+            META_LOCAL.extend(a.split(","))
+        if o == "-p":
+            if a == "-":
+                HTTP_PROXY = ""
+            else:
+                HTTP_PROXY = a
+        if o == "-r":
+            F_ROOT = a
+        if o == "-s":
+            SERVER = a
+        if o == "-v":
+            print "Blossom/%s" % __version__
+            sys.exit(0)
+        if o == "-w":
+            WEB_STATUS = a
+        if o == "-x":
+            PERSIST = 1
+        if o == "-y":
+            BLOSSOM_ARGS = ""
+
+    if ENABLE_DIR and not HTTP_PROXY:
+        log_msg(0, "directory servers require an HTTP Proxy (-p - disallowed)")
+        sys.exit(0)
+
+    if usage:
+        msg = "usage: %s [OPTIONS]\n" % sys.argv[0]
+        msg += "    -b list      comma-delimited list of Blossom servers <host:port>\n"
+        msg += "    -c host:port connect as Tor controller via <host:port>\n"
+        msg += "    -d num       debug to stdout at level <num>\n"
+        msg += "    -f file      run Blossom with local directory configured by <file>\n"
+        msg += "    -g           force generic directory queries (slower)\n"
+        msg += "    -i num       directory poll interval\n"
+        msg += "    -m list      comma-delimited list of metadata strings\n"
+        msg += "    -p host:port use <host:port> as an HTTP proxy\n"
+        msg += "    -r dir       use <dir> as the root directory for data files\n"
+        msg += "    -s host:port run client web server on host:port\n"
+        msg += "    -t minutes   time between periodic Blossom operations\n"
+        msg += "    -v           display version information\n"
+        msg += "    -w host:port specify alternate web status page\n"
+        msg += "    -x           establish persistent connections\n"
+        msg += "    -y           suppress requests for external metadata\n"
+        print msg
+        sys.exit(0)
+
+    sh, sp = parseHostAndPort(TORCONTROL)
+
+    BASE_URL            = "http://%s/cgi-bin/exit.pl?"; % WEB_STATUS
+    POLICY_URL          = BASE_URL + POLICY_ARGS
+    STATUS_URL          = BASE_URL + STATUS_ARGS
+    POLICY_URL_BLOSSOM  = BASE_URL + BLOSSOM_ARGS + POLICY_ARGS
+    STATUS_URL_BLOSSOM  = BASE_URL + BLOSSOM_ARGS + STATUS_ARGS
+
+    # start the client-side web server
+
+    dh, dp = parseHostAndPort(SERVER)
+    ClientRequestHandler.protocol_version = "HTTP/1.0"
+    httpd = ThreadingHTTPServer((dh, dp), ClientRequestHandler)
+    sa = httpd.socket.getsockname()
+    log_msg(2, "serving HTTP on %s:%s." % (sa[0], sa[1]))
+
+    # parse configuration file
+
+    if ENABLE_DIR:
+        try:
+            conflines = open(CONFFILE, "r").read().split("\n")
+            log_msg(2, "*** reading configuration file: %s" % CONFFILE)
+            parse_configuration(conflines)
+            dh, DIR_PORT = parseHostAndPort(DIR_SERVER)
+        except IOError, e:
+            log_msg(1, e)
+
+    try:
+        # retrieve nickname directly
+        sh, sp = parseHostAndPort(TORCONTROL)
+
+        conn = getConnection()
+        log_msg(3, "*** OPENING CONTROLLER CONNECTION")
+
+        INIT = 0
+
+        if ENABLE_DIR:
+            thread = DirectoryServiceThread()
+            thread.setDaemon(1)
+            thread.start()
+
+        # CONTROL LOOP
+
+        while 1:
+            try:
+                handle_callbacks_individually(httpd)
+            except BlossomError:
+                log_msg(0, "FATAL ERROR")
+                sys.exit(0)
+            except TorCtlClosed:
+                log_msg(1, "*** retrying controller connection [%s]" % get_curr_time())
+                CONNECTION_CLOSED = 0
+                time.sleep(TIME_RETRY)
+
+    except KeyboardInterrupt:
+        log_msg(1, "exiting on ^C [%s]\n" % get_curr_time())
+    return
+
+if __name__ == '__main__':
+    if os.name == "posix":
+        signal.signal(signal.SIGHUP, signal_handler)
+        signal.signal(signal.SIGTERM, signal_handler)
+        signal.signal(signal.SIGUSR1, signal_handler)
+        CONFFILE = os.environ['HOME'] + "/.blossomrc"
+        F_ROOT = os.environ['HOME'] + "/.blossom"
+    else:
+        CONFFILE = "./blossomrc"
+        F_ROOT = "."
+    F_COUNTRY_CODES     = "%s/country-codes.txt" % F_ROOT
+
+    main()
+


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

Added: blossom/trunk/country-codes.txt
===================================================================
--- blossom/trunk/country-codes.txt	                        (rev 0)
+++ blossom/trunk/country-codes.txt	2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,254 @@
+19   RFC 1918 Private
+33   RFC 3330 Reserved
+AD   Andorra
+AE   United Arab Emirates
+AF   Afghanistan
+AG   Antigua and Barbuda
+AI   Anguilla
+AL   Albania
+AM   Armenia
+AN   Netherlands Antilles
+AO   Angola
+AQ   Antarctica
+AR   Argentina
+AS   American Samoa
+AT   Austria
+AU   Australia
+AW   Aruba
+AZ   Azerbaijan
+BA   Bosnia and Herzegovina
+BB   Barbados
+BD   Bangladesh
+BE   Belgium
+BF   Burkina Faso
+BG   Bulgaria
+BH   Bahrain
+BI   Burundi
+BJ   Benin
+BM   Bermuda
+BN   Brunei Darussalam
+BO   Bolivia
+BR   Brazil
+BS   Bahamas
+BT   Bhutan
+BV   Bouvet Island
+BW   Botswana
+BY   Belarus
+BZ   Belize
+CA   Canada
+CC   Cocos (Keeling) Islands
+CF   Central African Republic
+CG   Congo
+CH   Switzerland
+CI   Cote D'Ivoire (Ivory Coast)
+CK   Cook Islands
+CL   Chile
+CM   Cameroon
+CN   China
+CO   Colombia
+CR   Costa Rica
+CS   Czechoslovakia (former)
+CU   Cuba
+CV   Cape Verde
+CX   Christmas Island
+CY   Cyprus
+CZ   Czech Republic
+DE   Germany
+DJ   Djibouti
+DK   Denmark
+DM   Dominica
+DO   Dominican Republic
+DZ   Algeria
+EC   Ecuador
+EE   Estonia
+EG   Egypt
+EH   Western Sahara
+ER   Eritrea
+ES   Spain
+ET   Ethiopia
+FI   Finland
+FJ   Fiji
+FK   Falkland Islands (Malvinas)
+FM   Micronesia
+FO   Faroe Islands
+FR   France
+FX   France, Metropolitan
+GA   Gabon
+GB   Great Britain (UK)
+GD   Grenada
+GE   Georgia
+GF   French Guiana
+GH   Ghana
+GI   Gibraltar
+GL   Greenland
+GM   Gambia
+GN   Guinea
+GP   Guadeloupe
+GQ   Equatorial Guinea
+GR   Greece
+GS   S. Georgia and S. Sandwich Isls.
+GT   Guatemala
+GU   Guam
+GW   Guinea-Bissau
+GY   Guyana
+HK   Hong Kong
+HM   Heard and McDonald Islands
+HN   Honduras
+HR   Croatia (Hrvatska)
+HT   Haiti
+HU   Hungary
+ID   Indonesia
+IE   Ireland
+IL   Israel
+IN   India
+IO   British Indian Ocean Territory
+IQ   Iraq
+IR   Iran
+IS   Iceland
+IT   Italy
+JM   Jamaica
+JO   Jordan
+JP   Japan
+KE   Kenya
+KG   Kyrgyzstan
+KH   Cambodia
+KI   Kiribati
+KM   Comoros
+KN   Saint Kitts and Nevis
+KP   Korea (North)
+KR   Korea (South)
+KW   Kuwait
+KY   Cayman Islands
+KZ   Kazakhstan
+LA   Laos
+LB   Lebanon
+LC   Saint Lucia
+LI   Liechtenstein
+LK   Sri Lanka
+LR   Liberia
+LS   Lesotho
+LT   Lithuania
+LU   Luxembourg
+LV   Latvia
+LY   Libya
+MA   Morocco
+MC   Monaco
+MD   Moldova
+MG   Madagascar
+MH   Marshall Islands
+MK   Macedonia
+ML   Mali
+MM   Myanmar
+MN   Mongolia
+MO   Macau
+MP   Northern Mariana Islands
+MQ   Martinique
+MR   Mauritania
+MS   Montserrat
+MT   Malta
+MU   Mauritius
+MV   Maldives
+MW   Malawi
+MX   Mexico
+MY   Malaysia
+MZ   Mozambique
+NA   Namibia
+NC   New Caledonia
+NE   Niger
+NF   Norfolk Island
+NG   Nigeria
+NI   Nicaragua
+NL   Netherlands
+NO   Norway
+NP   Nepal
+NR   Nauru
+NT   Neutral Zone
+NU   Niue
+NZ   New Zealand (Aotearoa)
+OM   Oman
+PA   Panama
+PE   Peru
+PF   French Polynesia
+PG   Papua New Guinea
+PH   Philippines
+PK   Pakistan
+PL   Poland
+PM   St. Pierre and Miquelon
+PN   Pitcairn
+PR   Puerto Rico
+PT   Portugal
+PW   Palau
+PY   Paraguay
+QA   Qatar
+RE   Reunion
+RO   Romania
+RU   Russian Federation
+RW   Rwanda
+SA   Saudi Arabia
+Sb   Solomon Islands
+SC   Seychelles
+SD   Sudan
+SE   Sweden
+SG   Singapore
+SH   St. Helena
+SI   Slovenia
+SJ   Svalbard and Jan Mayen Islands
+SK   Slovak Republic
+SL   Sierra Leone
+SM   San Marino
+SN   Senegal
+SO   Somalia
+SR   Suriname
+ST   Sao Tome and Principe
+SU   USSR (former)
+SV   El Salvador
+SY   Syria
+SZ   Swaziland
+TC   Turks and Caicos Islands
+TD   Chad
+TF   French Southern Territories
+TG   Togo
+TH   Thailand
+TJ   Tajikistan
+TK   Tokelau
+TM   Turkmenistan
+TN   Tunisia
+TO   Tonga
+TP   East Timor
+TR   Turkey
+TT   Trinidad and Tobago
+TV   Tuvalu
+TW   Taiwan
+TZ   Tanzania
+UA   Ukraine
+UG   Uganda
+UK   United Kingdom
+UM   US Minor Outlying Islands
+US   United States
+UY   Uruguay
+UZ   Uzbekistan
+VA   Vatican City State (Holy See)
+VC   Saint Vincent and the Grenadines
+VE   Venezuela
+VG   Virgin Islands (British)
+VI   Virgin Islands (U.S.)
+VN   Viet Nam
+VU   Vanuatu
+WF   Wallis and Futuna Islands
+WS   Samoa
+YE   Yemen
+YT   Mayotte
+YU   Yugoslavia
+ZA   South Africa
+ZM   Zambia
+ZR   Zaire
+ZW   Zimbabwe
+COM   US Commercial
+EDU   US Educational
+GOV   US Government
+INT   International
+MIL   US Military
+NET   Network
+ORG   Non-Profit Organization
+ARPA   Old style Arpanet
+NATO   Nato field

Added: blossom/trunk/exit.pl
===================================================================
--- blossom/trunk/exit.pl	                        (rev 0)
+++ blossom/trunk/exit.pl	2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,1231 @@
+#!/usr/bin/perl -w
+# $Id: exit.pl,v 1.36 2006-07-06 01:35:47 goodell Exp $
+$license = <<EOF
+Copyright (c) 2005-2006 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 @portslist       = (22, 53, 80, 110, 119, 143, 443, 5190, 6667);
+my @os_names        = ("Cygwin",
+                       "Darwin",
+                       "DragonFly",
+                       "FreeBSD",
+                       "IRIX64",
+                       "Linux",
+                       "NetBSD",
+                       "OpenBSD",
+                       "SunOS",
+                       "Windows",
+                       "Unknown");
+
+my $CONFIG          = "/etc/exit.conf";
+my $CACHE           = "/var/cache/www-data";
+my $A_STANDARD      = "standard";
+my $A_UNVERIFIED    = "unverified";
+my $A_AUTHORITY     = "failedstream";
+my $TD_DEFAULT      = "td";
+my $TITLE           = "Tor Network Status";
+my $URL_FLAGS       = "http://afs.eecs.harvard.edu/~goodell/flags";;
+my $URL_ICONS       = "http://afs.eecs.harvard.edu/~goodell/icons";;
+my $URL_OSICONS     = "http://afs.eecs.harvard.edu/~goodell/os-icons";;
+my $URL_SOURCE      = "http://afs.eecs.harvard.edu/~goodell/blossom/src/exit.pl";;
+my $URL_HOME        = "http://tor.eff.org/";;
+my $URL_DIRECTORY   = "http://localhost:9030/tor/";;
+my $URL_STATUS      = "http://localhost:9030/tor/running-routers";;
+my $URL_V2DIR       = "http://localhost:9030/tor/status/all";;
+my $URL_WHOIS       = "http://alsatia.eecs.harvard.edu/cgi-bin/whois.pl\?q=";;
+my $LINK_DIRECTORY  = "http://serifos.eecs.harvard.edu:9030/tor/";;
+my $LINK_STATUS     = "http://serifos.eecs.harvard.edu:9030/tor/running-routers";;
+my $F_CCODES        = "/afs/eecs.harvard.edu/user/goodell/public_html/country-codes.txt";
+my $WGET            = "/usr/bin/wget -O -";
+my $WHOIS           = "/usr/bin/whois";
+my $DESC_SCRIPT     = "/cgi-bin/desc.pl";
+my $WHOIS_SCRIPT    = "/cgi-bin/whois.pl";
+my $ICON_HN         = "hn.gif";
+my $ICON_UR         = "ur.gif";
+my $ICON_S0         = "s0.gif";
+my $ICON_S1         = "s1.gif";
+my $ICON_V0         = "v0.gif";
+my $ICON_V1         = "v1.gif";
+my $ICON_V2         = "v2.gif";
+my $ICON_V3         = "v3.gif";
+my $P_FLAGS         = "width=18 height=12";
+my $P_ICONS         = "width=12 height=12";
+my $UV_TEXT         = "officially named";
+my $SYS             = "Tor";
+my $DEFAULT_PORT    = "9030";
+my $HTTP_PROXY      = "";
+my $DESC_DIR        = "";
+my $BLOSSOM         = 0;
+my $CACHEDAYS       = 90;
+my $CACHESECONDS    = 300;
+my $MAX_PORT        = 65535;
+my $DNS_EXPIRATION  = 86400;
+my $MAXNETLENGTH    = 26;
+my $MAXNICKLENGTH   = 20;
+my $MAXHOSTLENGTH   = 58;
+my $V1_MINBW        = 20;
+my $V2_MINBW        = 60;
+my $V3_MINBW        = 400;
+
+do $CONFIG if -r $CONFIG;
+
+# other global variables
+
+my %bw              = ();
+my %ccode           = ();
+my %directory       = ();
+my %hops            = ();
+my %hr              = ();
+my %hw              = ();
+my %oses            = ();
+my %summary         = ();
+my %tr              = ();
+my %tw              = ();
+my %uptime          = ();
+
+my %cc_matches      = ();
+my %dns_time        = ();
+my %dns_name        = ();
+
+my $DISPLAY_ADDR    = undef;
+my $TEXTONLY        = undef;
+my $SORTBW          = undef;
+
+# variables for parsing the URI
+
+my %fields          = ();
+
+my $uri             = "";
+my $dnscachefile    = "dnscache";
+my $invalid         = undef;
+my $response        = "";
+my $uri_text        = "";
+my $uri_addr        = "";
+my $uri_sort        = "";
+my $uri_blossom     = "";
+my $link_addr       = "";
+my $link_sort       = "";
+my $success         = undef;
+
+# variables for parsing descriptors
+
+my %all_r           = ();
+my %all_u           = ();
+my %blossom_path    = ();
+my %lines           = ();
+my %num             = ();
+
+my @addrbytes       = ();
+my @lines           = ();
+my @policy          = ();
+my @running_routers = ();
+
+my $addr            = "";
+my $cc              = "";
+my $class           = "";
+my $bandwidth       = "";
+my $dpublished      = "";
+my $dsignature      = "";
+my $fingerprint     = "";
+my $host            = "";
+my $iaddr           = "";
+my $icon            = "";
+my $netname         = "";
+my $p_router        = "";
+my $router          = "";
+my $router_td       = "";
+my $platformline    = "";
+my $service         = "";
+my $spublished      = "";
+my $system          = "";
+my $version         = "";
+
+my $hibernating     = undef;
+my $named           = undef;
+
+my $fpublished      = 1;
+my $maxlength       = 0;
+my $maxname         = 0;
+my $orport          = 0;
+my $socksport       = 0;
+my $dirport         = 0;
+my $minfast         = 4294967295;
+
+$num{"uhi"}         = 0;
+$num{"uur"}         = 0;
+$num{"uv0"}         = 0;
+$num{"uv1"}         = 0;
+$num{"uv2"}         = 0;
+$num{"uv3"}         = 0;
+$num{"u"}           = 0;
+$num{"vhi"}         = 0;
+$num{"vur"}         = 0;
+$num{"vv0"}         = 0;
+$num{"vv1"}         = 0;
+$num{"vv2"}         = 0;
+$num{"vv3"}         = 0;
+$num{"v"}           = 0;
+
+use vars qw($license);
+
+# subroutines
+
+sub license() {
+    print $license;
+    exit 0;
+}
+
+sub addrouters($) {
+    my @sorted      = undef;
+    my $href        = shift;
+    my $response    = "";
+    my %routers     = %$href;
+
+    if($SORTBW) {
+        @sorted = sort { $uptime{$a} <=> $uptime{$b} } keys %routers;
+        @sorted = sort { $hr{$a} + $hw{$a} <=> $hr{$a} + $hw{$b} } @sorted;
+        @sorted = reverse sort { $tr{$a} + $tw{$a} <=> $tr{$b} + $tw{$b} } @sorted;
+    } else {
+        @sorted = sort keys %routers;
+    }
+
+    foreach my $router (@sorted) {
+        if($TEXTONLY) {
+            $response .= $routers{$router};
+        } else {
+            $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;
+
+    if($matches[$#matches]) {
+        chomp $matches[$#matches];
+        ($t = $matches[$#matches]) =~ s/\S+\s+//g;
+    } else {
+        $t = $default;
+    }
+    return $t;
+}
+
+sub add_field($$) {
+    my ($uri, $field) = (shift, shift);
+
+    print STDERR "$uri\n";
+
+    if($uri !~ /^$/) {
+        $uri .= "&";
+    }
+    return $uri . $field;
+}
+
+sub remove_field($$) {
+    my ($uri, $field) = (shift, shift);
+    my @prompts = split /&/, $uri;
+    my @returns = ();
+
+    foreach my $prompt (@prompts) {
+        push @returns, $prompt unless $prompt =~ /^$field=/;
+    }
+    return join "&", @returns;
+}
+
+sub padded_cell($;$) {
+    my ($length, $class) = (shift, shift);
+    my $tdclass = $class ? "<td class=\"$class\">" : "<td>";
+    my $output = "$tdclass<tt>";
+
+    for(my $x = 0; $x < $length; $x++) {
+        $output .= "&nbsp;";
+    }
+    $output .= "</tt></td>";
+    return $output;
+}
+
+sub sum($) {
+    my @terms = split /,/, shift;
+    my $total = 0;
+    foreach my $term (@terms) {
+        $term += 2<<32 if $term > 0;
+        $total += $term;
+    }
+    return $total;
+}
+
+sub by_totals {
+    my $aa = $oses{$a}[0] + $oses{$a}[1];
+    my $bb = $oses{$b}[0] + $oses{$b}[1];
+
+    if($aa == $bb) {
+        return $oses{$a}[0] <=> $oses{$b}[0];
+    } else {
+        return $aa <=> $bb;
+    }
+}
+
+sub reset_globals {
+    $class          = $TD_DEFAULT;
+    $p_router       = undef;
+    $router         = undef;
+    $router_td      = undef;
+    $icon           = "<img $P_FLAGS src=\"$URL_ICONS/$ICON_V0\" alt=\"v0\">";
+    $orport         = 0;
+    $socksport      = 0;
+    $dirport        = 0;
+    $hibernating    = undef;
+    $named          = undef;
+
+    @lines          = ();
+    @policy         = ();
+}
+
+sub make_flag($) {
+    my $cc = shift;
+    my $ccy = lc $cc;
+    my $f_ccode = $ccode{$cc} || "~~";
+    my $flag = "<img $P_FLAGS src=\"$URL_FLAGS/$ccy.gif\" alt=\"$cc\">";
+    return "<acronym title=\"$f_ccode\">$flag</acronym>";
+}
+
+# parse the URI parameters
+
+if($ENV{"REQUEST_URI"} && $ENV{"REQUEST_URI"} =~ /\?/) {
+    ($uri = $ENV{"REQUEST_URI"}) =~ s/.*\?//g;
+}
+
+my @prompts = split /&/, $uri;
+
+open DNSCACHE, "<$CACHE/$dnscachefile";
+while(<DNSCACHE>) {
+    if(/^(\S+)\s+(\S+)\s+(\S+)$/) {
+        $dns_time{$2} = $1;
+        $dns_name{$2} = $3;
+    }
+}
+close DNSCACHE;
+
+foreach (@prompts) {
+    my ($k, $v) = split /=/, $_;
+    $fields{$k} = $v;
+}
+
+if($fields{"sortbw"}) {
+    $SORTBW = 1;
+    $uri_sort = remove_field($uri, "sortbw");
+    $link_sort = "by country";
+} else {
+    $uri_sort = add_field($uri, "sortbw=1");
+    $link_sort = "by bandwidth";
+}
+
+if($fields{"ports"}) {
+    if($fields{"ports"} !~ /^([0-9]+,)*[0-9]+$/) {
+        $invalid = 1;
+    } else {
+        @portslist = split /,/, $fields{"ports"};
+        foreach my $port (@portslist) {
+            $invalid = 1 if $port > $MAX_PORT;
+        }
+    }
+}
+
+if($fields{"textonly"}) {
+    $TEXTONLY = $fields{"textonly"};
+} else {
+    $uri_text = add_field($uri, "textonly=1");
+}
+
+if($fields{"addr"}) {
+    $DISPLAY_ADDR = 1;
+    $uri_addr = remove_field($uri, "addr");
+    $link_addr = "show hostnames";
+} else {
+    $uri_addr = add_field($uri, "addr=1");
+    $link_addr = "show addresses";
+}
+
+if($fields{"blossom"}) {
+    my $B_PORT      = $DEFAULT_PORT;
+    $uri_blossom    = remove_field($uri, "blossom");
+    $BLOSSOM        = $fields{"blossom"};
+
+    if($fields{"textonly"}
+            and $fields{"textonly"} eq "fingerprint"
+            and $fields{"addr"}
+            and not $fields{"sortbw"}) {
+    }
+
+    if($BLOSSOM =~ /^([A-Za-z0-9]+):([0-9]+)$/) {
+        $BLOSSOM = $1;
+        $B_PORT = $2;
+    }
+
+    $TITLE          = "Blossom Network Status: $BLOSSOM";
+    $URL_DIRECTORY  = "http://$BLOSSOM.exit:$B_PORT/tor/";;
+    $URL_STATUS     = "http://$BLOSSOM.exit:$B_PORT/tor/running-routers";;
+    $LINK_DIRECTORY = "http://$BLOSSOM.exit:$B_PORT/tor/";;
+    $LINK_STATUS    = "http://$BLOSSOM.exit:$B_PORT/tor/running-routers";;
+    $HTTP_PROXY     = "http_proxy=http://localhost:8119 ";
+    $DESC_DIR       = "$BLOSSOM:$B_PORT";
+    $V1_MINBW       = 0;
+    $V2_MINBW       = 4;
+    $V3_MINBW       = 60;
+    $UV_TEXT        = "directory server";
+    $URL_HOME       = "http://afs.eecs.harvard.edu/~goodell/blossom/";;
+    $SYS            = "Blossom";
+}
+
+if($invalid) {
+    print "Content-type: text/plain\n\n";
+    print "invalid input: " . $fields{"ports"} . "\n";
+    exit -1;
+}
+
+# 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
+
+$uri_text =~ s/&/&amp;/g;
+$uri_addr =~ s/&/&amp;/g;
+$uri_sort =~ s/&/&amp;/g;
+
+if(defined $TEXTONLY) {
+    $response = <<EOF
+Content-type: text/plain
+
+$TITLE
+
+EOF
+;
+} else {
+    $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>
+
+<h1>$TITLE</h1>
+
+<p><tt>
+    [<a href="#legend">explanation of symbols</a>]
+    [<a href="?$uri_text">text only version</a>]
+    [<a href="?$uri_addr">$link_addr</a>]
+    [<a href="?$uri_sort">$link_sort</a>]
+</tt></p>
+
+<table>
+
+EOF
+;
+}
+
+# parse the descriptor
+
+open W, "$HTTP_PROXY$WGET $URL_STATUS |" || die;
+
+while(<W>) {
+    if(/^published (.*)$/) {
+        $spublished = "<a href=\"$LINK_STATUS\">network status</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$1";
+    } elsif(/^router-status /) {
+        @running_routers = split;
+        shift @running_routers;
+        for(my $x = 0; $x <= $#running_routers; $x++) {
+            $running_routers[$x] =~ s/=.*$//g;
+            $running_routers[$x] =~ y/A-Z/a-z/;
+        }
+    }
+}
+
+close W;
+
+# parse version 2 directory data
+
+open W, "$HTTP_PROXY$WGET $URL_V2DIR |" || die;
+
+my $trtr = "";
+my %types = ();
+
+while(<W>) {
+    if(/^r (\S+) /) {
+        $trtr = lc $1;
+        %{$types{$trtr}} = () unless $types{$trtr};
+    } elsif(/^s (.*)$/) {
+        my @characteristics = split / /, $1;
+        foreach my $characteristic (@characteristics) {
+            ${$types{$trtr}}{$characteristic} = 1;
+        }
+    }
+}
+
+# parse the descriptors
+
+open W, "$HTTP_PROXY$WGET $URL_DIRECTORY |" || die;
+
+while(<W>) {
+    chomp;
+    $success = 1;
+
+    if(/^published (.*)$/ and $fpublished) {
+        $dpublished = "<a href=\"$LINK_DIRECTORY\">directory published</a>&nbsp;$1";
+        $fpublished = undef;
+    } elsif(/^directory-signature (\S+)$/) {
+        $dsignature = lc $1;
+    } elsif(/^directory (\S+)\s+(\S+)\s*(\s+\S+)?$/) {
+        $directory{$1} = $2;
+    } elsif(/^summary (\S+)\s+(\S+)$/) {
+        $summary{$1} = $2;
+    } elsif(/^blossom-path (\S+)\s+(.+)$/) {
+        $blossom_path{$1} = $2;
+    } elsif(/^router (\S+) (\S+) (\S+) (\S+) (\S+)$/) {
+        ($router, $addr, $orport, $socksport, $dirport) = ($1, $2, $3, $4, $5);
+        $router = lc $router;
+        $p_router = $router;
+
+        $iaddr = inet_aton($addr);
+        @addrbytes = split /\./, $addr;
+        for(my $i = 0; $i < 4; $i++) {
+            $addrbytes[$i] = int($addrbytes[$i]);
+        }
+
+        if(defined $DISPLAY_ADDR) {
+            $host = sprintf "%-15s", $2;
+            $host =~ s/ /&nbsp;/g if not defined $TEXTONLY;
+        } else {
+            my ($a, $b, $c, $d) = unpack 'C4', $iaddr;
+            my $bytes = "$a.$b.$c.$d";
+            if($dns_name{$bytes} and $dns_time{$bytes} and $dns_time{$bytes} > time - $DNS_EXPIRATION) {
+                $host = $dns_name{$bytes};
+            } else {
+                print STDERR "DNS lookup: $bytes...";
+                ($host = gethostbyaddr($iaddr, AF_INET) || $2) =~ y/A-Z/a-z/;
+                my ($name, $aliases, $addpe, $length, @addrs) = gethostbyname($host);
+                print STDERR "done.\n";
+
+                if(not $addrs[0] or $addrs[0] ne $iaddr) {
+                    $host = $bytes;
+                }
+                $dns_time{$bytes} = time;
+                $dns_name{$bytes} = $host;
+            }
+        }
+        $service = 1;
+    } elsif(/^platform Tor (\S+) on (\S+)(\s(\S+))?/) {
+        $version = substr $1, 0, 20;
+        $system = $2;
+        my $extra = "";
+        if($4) {
+            $extra = $4;
+        }
+        ($platformline = $_) =~ s/^platform //;
+        $platformline =~ s/\"//g;
+
+        $system = "Other" unless grep /^$system$/, @os_names;
+        if($system eq "Windows") {
+            if($extra eq "Server" or $extra eq "XP") {
+                $system .= $extra;
+            } else {
+                $system .= "Other";
+            }
+        }
+        $system =~ s/\"//g;
+        unless(defined $oses{$system}) {
+            my @zeroes = (0, 0);
+            $oses{$system} = \@zeroes;
+        }
+    } elsif(/^(opt\s+)?uptime\s+([0-9]+)$/) {
+        $uptime{$router} = $2;
+    } elsif(/^(opt\s+)?hibernating\s+([0-9]+)$/) {
+        $hibernating = $2;
+    } elsif(/^(opt\s+)?fingerprint\s+(.+)$/) {
+        ($fingerprint = $2) =~ s/ //g;
+        $fingerprint =~ y/a-z/A-Z/;
+    } elsif(/^bandwidth ([0-9]+) ([0-9]+) ([0-9]+)$/) {
+        if($1 > $3) {
+            $bandwidth = $3;
+        } else {
+            $bandwidth = $1;
+        }
+    } elsif(/^(opt\s+)?read-history\s+\S+\s+\S+\s+\(([0-9]+)\s+s\)\s+(\S+)$/) {
+        my @terms = split /,/, $3;
+        $tr{$router} = sum($3);
+        $hr{$router} = $tr{$router} / ($#terms+1) / $2;
+    } elsif(/^(opt\s+)?write-history\s+\S+\s+\S+\s+\(([0-9]+)\s+s\)\s+(\S+)$/) {
+        my @terms = split /,/, $3;
+        $tw{$router} = sum($3);
+        $hw{$router} = $tw{$router} / ($#terms+1) / $2;
+    } elsif(/^accept \S+$/) {
+        push @policy, $_;
+    } elsif(/^reject \S+$/) {
+        push @policy, $_;
+    } elsif(/^$/) { # ----- BEGIN ROUTER PROCESSING SECTION -----
+        my $cache_succ  = undef;
+        my $command     = undef;
+        my $t           = undef;
+
+        my $atag        = $A_STANDARD;
+        my $b           = $directory{$router};
+        my $length      = 0;
+        my $whois_proxy = 0;
+        my $slen        = 0;
+        my $wrapper     = "";
+
+        my @netlast     = ();
+        my @matches     = ();
+
+        unless($router){
+            reset_globals();
+            next;
+        }
+
+        # support old scheme for determining hibernating pre-0.1 routers
+
+        if(($version =~ /^0\.0\./)
+        && ($uptime{$router} ne "0")
+        && ($dirport eq "0")
+        && ($bandwidth eq "0")) {
+            $hibernating = 1;
+        }
+
+        # obtain WHOIS data: check the cache first
+
+        if(-e "$CACHE/$addr") {
+            my ($size, $modified) = (stat "$CACHE/$addr")[7, 9];
+            $command = "<$CACHE/$addr" if $size && $size > 600 && $modified > time-86400*$CACHEDAYS;
+        }
+
+        # otherwise obtain WHOIS data from the Internet
+
+        if(not defined $command) {
+            $command = "$WHOIS $addr |";
+            $t = 1;
+        }
+        open X, $command || warn;
+
+        $cache_succ = open Y, ">$CACHE/$addr" if $t;
+        while(<X>) {
+
+            # conditions for using an external proxy to conduct WHOIS lookups
+
+            # if($t and /^% This is the RIPE Whois query server #2\.$/) {
+            #     $t = 0;
+            #     $whois_proxy = 1;
+            #     last;
+            # }
+
+            push @lines, $_;
+            push @netlast, $1 if /\((NET-\S+)\)/;
+            print Y if $t && $cache_succ;
+        }
+        close X;
+
+        # temporary workaround for permanent filtering
+
+        if($whois_proxy) {
+            @lines = ();
+            open X, "$WGET $URL_WHOIS$addr |" or warn;
+            while(<X>) {
+                push @lines, $_;
+                print Y if $cache_succ;
+            }
+            close X;
+        }
+
+        while(($#netlast > 0) and $t) {
+            my $f = 1;
+            my $netguess = pop @netlast;
+            next unless $netguess and $netguess =~ /^[A-Za-z0-9-.]+$/;
+            open X, "$WHOIS $netguess |" || warn;
+
+            while(<X>) {
+                push @lines, $_;
+                print Y if $cache_succ;
+                $f = 0 if /\((NET-\S+)\)/;
+            }
+            close X;
+            last if $f;
+        }
+        close Y if $cache_succ;
+
+        # parse router statistics
+
+        @matches = grep /^!?$router$/, @running_routers;
+        if(not defined $matches[0]) {
+        #     if($BLOSSOM == 0) {
+        #         $class = "td class=\"$A_UNVERIFIED\"";
+        #         $atag = $A_UNVERIFIED;
+        #         $p_router = "*$router";
+        #         $service = undef;
+        #     }
+            @matches = grep /^!?\$$fingerprint/, @running_routers;
+        }
+
+        if($types{$router} and ${$types{$router}}{"Named"}) {
+            $class = "td class=\"$A_UNVERIFIED\"";
+            $atag = $A_UNVERIFIED;
+            $p_router = "*$router";
+            $named = 1;
+        }
+
+        if($b) {
+            $class = "td class=\"$A_UNVERIFIED\"";
+            $atag = $A_UNVERIFIED;
+            $p_router = "*$router";
+        }
+
+        if($types{$router} and ${$types{$router}}{"Authority"}) {
+            $class = "td class=\"$A_AUTHORITY\"";
+            $atag = $A_STANDARD;
+            $p_router = "*$router";
+            $named = 1;
+        }
+
+        if($matches[0] && $matches[0] =~ /^!/) {
+            $bandwidth = 0;
+            if($hibernating) {
+                $icon = "<acronym title=\"hibernating\"><img $P_FLAGS src=\"$URL_ICONS/$ICON_HN\" alt=\"hn\"></acronym>";
+                $num{"uhi"}++ if $named or $b;
+                $num{"vhi"}++;
+                $bw{$router} = -1;
+            } else {
+                $icon = "<acronym title=\"unresponsive\"><img $P_FLAGS src=\"$URL_ICONS/$ICON_UR\" alt=\"ur\"></acronym>";
+                $num{"uur"}++ if $named or $b;
+                $num{"vur"}++;
+                $bw{$router} = -2;
+            }
+            $oses{$system}[1]++ if $named or $b;
+            $oses{$system}[0]++;
+            $service = undef;
+        } else {
+            my $tooslow = undef;
+            $bw{$router} = $bandwidth;
+            if(not $types{$router} or not ${$types{$router}}{"Fast"}) {
+                $num{"uv0"}++ if $named or $b;
+                $num{"vv0"}++;
+                $tooslow = 1;
+            } elsif($bandwidth >= $V3_MINBW*1000) {
+                $icon = "<img $P_FLAGS src=\"$URL_ICONS/$ICON_V3\" alt=\"v3\">";
+                $num{"uv3"}++ if $named or $b;
+                $num{"vv3"}++;
+            } elsif($bandwidth >= $V2_MINBW*1000) {
+                $icon = "<img $P_FLAGS src=\"$URL_ICONS/$ICON_V2\" alt=\"v2\">";
+                $num{"uv2"}++ if $named or $b;
+                $num{"vv2"}++;
+            } else {
+                $icon = "<img $P_FLAGS src=\"$URL_ICONS/$ICON_V1\" alt=\"v1\">";
+                $num{"uv1"}++ if $named or $b;
+                $num{"vv1"}++;
+                $minfast = $bandwidth if $bandwidth < $minfast;
+            }
+            $icon = "<acronym title=\"$bandwidth B/s\">$icon</acronym>";
+            $oses{$system}[1]++ if $named or $b;
+            $oses{$system}[0]++;
+            $service = undef if $tooslow and ($BLOSSOM == 0);
+        }
+
+        # determine the network name from WHOIS results
+
+        $netname = parsewhois("netname", "", \@lines);
+        $netname = $addr if length($netname) > $MAXHOSTLENGTH - 16;
+
+        # determine the country (and correct network name if necessary)
+
+        $t = parsewhois("country", undef, \@lines);
+
+        # RFC 1918
+
+        if($addrbytes[0] == 10
+                or ($addrbytes[0] == 172
+                    and $addrbytes[1] > 15
+                    and $addrbytes[1] < 32)
+                or ($addrbytes[0] == 192
+                    and $addrbytes[1] == 168)) {
+            $t = "19";
+        }
+
+        # RFC 3330
+
+        if($addrbytes[0] == 0
+                or $addrbytes[0] == 127
+                or ($addrbytes[0] == 169
+                    and $addrbytes[1] == 254)) {
+            $t = "33";
+        }
+
+        unless($t) {
+            $t = "~~";
+
+            # Exception: Brazil
+            if(parsewhois("% Copyright registro.br", undef, \@lines)) {
+                $t = "BR";
+                $netname = parsewhois("aut", "", \@lines);
+            }
+
+            # Exception: Japan
+            if(parsewhois("\\\[ JPNIC database", undef, \@lines)) {
+                $t = "JP";
+                $netname = parsewhois("b. \\\[Network Name\\\]", "", \@lines);
+            }
+
+            # Exception: Korea
+            if(parsewhois("# KOREAN", undef, \@lines)) {
+                $t = "KR";
+                $netname = parsewhois("Service Name", "", \@lines);
+            }
+        }
+        ($cc = $t) =~ y/a-z/A-Z/;
+
+        $netname = $addr if $netname eq "";
+
+        # generate the wrapper for the host information column
+
+        $t = "";
+        if($SORTBW) {
+            my $days = int ($uptime{$router}/86400);
+            $days = "err" if $days > 999;
+
+            $slen = 1;
+            # $t = sprintf "%7s B/s %3sd ", $bandwidth, $days;
+            $t = sprintf "%4s %4s %4s ",
+                    int($bw{$router}/1000), int($hr{$router}/1000), int($hw{$router}/1000);
+            $t = " err  err  err " if length $t > 15;
+            my $tt = sprintf "%4s ", int($uptime{$router}/86400);
+            if(length $tt > 5) {
+                $t .= " err ";
+            } else {
+                $t .= $tt;
+            }
+            $wrapper = $t;
+            $wrapper =~ s/ /&nbsp;/g if not $TEXTONLY;
+            $host = $wrapper . $host;
+            if($DISPLAY_ADDR) {
+                $host .= " $version";
+                $host =~ s/ /&nbsp;/g if not $TEXTONLY;
+            }
+        } elsif(not $TEXTONLY) {
+            $slen = 3;
+            $t = $netname;
+            $wrapper = "&nbsp;[<a class=\"$atag\" href=\"$WHOIS_SCRIPT?q=$addr\">$t</a>]";
+            $host .= $wrapper;
+        }
+        $length = $slen + (length $host)-(length $wrapper)+(length $t);
+
+        # format the host information to satisfy length constraints
+
+        if(($length > $MAXHOSTLENGTH) && (not $DISPLAY_ADDR) && (not $TEXTONLY)) {
+            if($SORTBW) {
+                (my $name = $host) =~ s/^.*;//g;
+                $host = substr $name, $length-$MAXHOSTLENGTH;
+                $host =~ s/^(\S+?)\./\*\./;
+                ($host = $t . $host) =~ s/ /&nbsp;/g;
+            } else {
+                $host = substr $host, $length-$MAXHOSTLENGTH;
+                $host =~ s/^(\S+?)\./\*\./;
+            }
+        }
+        if($DISPLAY_ADDR) {
+            if($SORTBW) {
+                $length = $slen + 36 + (length $version);
+            } else {
+                $length = $slen + 16 + (length $t);
+            }
+        } else {
+            $length = $slen + (length $host)-(length $wrapper)+(length $t);
+        }
+
+        $maxlength = $length if $maxlength < $length;
+
+        $p_router = substr $p_router, 0, $MAXNICKLENGTH if length $router > $MAXNICKLENGTH;
+        if($BLOSSOM) {
+            if($b) {
+                my $b_port = ($b == $DEFAULT_PORT) ? "" : ":$b";
+                my $uri_redirect = add_field($uri_blossom, "blossom=$router$b_port");
+                $router_td = "<a class=\"$atag\" href=\"?$uri_redirect\">$p_router</a>";
+            } else {
+                $router_td = "<a class=\"$atag\" href=\"$DESC_SCRIPT?q=$router&amp;blossom=$DESC_DIR\">$p_router</a>";
+            }
+        } else {
+            $router_td = "<a class=\"$atag\" href=\"$DESC_SCRIPT?q=$router\">$p_router</a>";
+        }
+        $maxname = length $p_router if $maxname < length $p_router;
+
+        if($SORTBW) {
+            $router_td = sprintf "%s&nbsp;%s", make_flag($cc), $router_td;
+        } else {
+            if($cc =~ /US|CA/) {
+                $t = parsewhois("stateprov", "&nbsp;&nbsp;", \@lines);
+                $t = "~~" unless $t =~ /^[A-Z][A-Z]$/;
+                $router_td = "$t&nbsp;$router_td&nbsp;";
+            } else {
+                $router_td = "&nbsp;&nbsp;&nbsp;$router_td&nbsp;";
+            }
+        }
+        $cc_matches{$cc} = 0 if not defined $cc_matches{$cc};
+
+        # ----- END ROUTER PROCESSING SECTION -----
+
+        my %accept   = ();
+
+        # format the output for this router
+
+        if($TEXTONLY) {
+            my $format = "%2s %-${MAXNICKLENGTH}s ";
+
+            $p_router = substr $p_router, 0, $MAXNICKLENGTH;
+            if($DISPLAY_ADDR and not $SORTBW) {
+                my $tformat = "%7s B/s %-15s %-${MAXNETLENGTH}s";
+                $netname = substr $netname, 0, $MAXNETLENGTH;
+                $host = sprintf $tformat, $bandwidth, $host, $netname;
+                $format .= "%s";
+            } else {
+                $host = substr $host, 0, $MAXHOSTLENGTH;
+                $format .= "%-${MAXHOSTLENGTH}s";
+            }
+            ${$lines{$cc}}{$router} = sprintf $format, $cc, $p_router, $host;
+        } else {
+            ${$lines{$cc}}{$router} = "<$class><tt>$router_td</tt></td><$class><tt>$icon&nbsp;$host</tt></td>\n";
+        }
+
+        # parse exit policy
+
+        foreach my $PORT (@portslist) {
+            for(my $x = 0; $x <= $#policy; $x++) {
+                if($policy[$x] =~ /^(\S+) \*:([0-9]+)-([0-9]+)$/) {
+                    if ($2 <= $PORT && $3 >= $PORT) {
+                        $accept{$PORT} = 1 if $1 eq "accept";
+                        last;
+                    }
+                } elsif($policy[$x] =~ /^(\S+) \*:$PORT$/) {
+                    $accept{$PORT} = 1 if $1 eq "accept";
+                    last;
+                } elsif($policy[$x] =~ /^(\S+) \*:\*$/) {
+                    $accept{$PORT} = 1 if $1 eq "accept";
+                    last;
+                }
+            }
+        }
+
+        unless($TEXTONLY) {
+            ${$lines{$cc}}{$router} .= "    <td class=\"centered\"><acronym title=\"$platformline\"><img $P_ICONS src=\"$URL_OSICONS/$system.png\" alt=\"*\"></acronym></td>\n";
+        }
+
+        # compose exit policy output
+
+        my $f = undef;
+
+        if($TEXTONLY and $TEXTONLY eq "fingerprint") {
+            ${$lines{$cc}}{$router} .= " $fingerprint";
+        } else {
+            foreach my $PORT (@portslist) {
+                if($TEXTONLY) {
+                    if(defined $accept{$PORT}) {
+                        ${$lines{$cc}}{$router} .= sprintf "%5s", $PORT;
+                        $f = 1;
+                    } else {
+                        ${$lines{$cc}}{$router} .= "    -";
+                    }
+                } else {
+                    if(defined $accept{$PORT}) {
+                        if($service) {
+                            ${$lines{$cc}}{$router} .= "    <td class=\"entry\"><tt>$PORT</tt></td>\n";
+                        } else {
+                            ${$lines{$cc}}{$router} .= "    <td class=\"dimentry\"><tt>$PORT</tt></td>\n";
+                        }
+                    } else {
+                        ${$lines{$cc}}{$router} .= sprintf "    %s\n", padded_cell(4);
+                    }
+                }
+            }
+        }
+        ${$lines{$cc}}{$router} .= "\n" if defined $TEXTONLY;
+        delete ${$lines{"~~"}}{$router} if ${$lines{"~~"}}{$router};
+        delete $lines{"~~"} unless (keys %{$lines{"~~"}});
+        $hops{$router} = -1;
+        $cc_matches{$cc}++ if $f;
+
+        # display Blossom summaries
+
+        if($summary{$router}) {
+            foreach my $elt (split /,/, $summary{$router}) {
+                my $num_hops = "1";
+                my $router_elt = $router;
+                if($elt =~ /^(\S+)=([0-9]+)$/) {
+                    $router_elt = $1;
+                    $num_hops = $2;
+                }
+
+                next if $router_elt eq $router;
+
+                @matches = grep /^!?$router_elt$/, @running_routers;
+                if(defined $matches[0]) {
+                    $icon = "<img $P_FLAGS src=\"$URL_ICONS/$ICON_S1\" alt=\"s1\">";
+                } else {
+                    $icon = "<img $P_FLAGS src=\"$URL_ICONS/$ICON_S0\" alt=\"s0\">";
+                }
+                if((not $hops{$router_elt}) or $hops{$router_elt} > int $num_hops) {
+
+                    $hops{$router_elt} = int $num_hops;
+                    %{$lines{"~~"}} = () unless $lines{"~~"};
+
+                    if($TEXTONLY) {
+                        ${$lines{"~~"}}{$router_elt} = "        $router_elt\n";
+                    } else {
+                        ${$lines{"~~"}}{$router_elt} = "    <td><tt>&nbsp;&nbsp;&nbsp;$router_elt&nbsp;</tt></td>\n";
+                        ${$lines{"~~"}}{$router_elt} .= "    <td><tt>$icon&nbsp;$num_hops via $router</tt></td>\n";
+                    }
+
+                }
+            }
+        }
+
+        # reset the control variables for the next router
+
+        reset_globals();
+    }
+}
+close W;
+
+# compose the table of results
+
+my $f;
+foreach $cc (sort keys %lines) {
+    if($cc !~ /^$/) {
+        my $flag = make_flag($cc);
+        my $nodes = scalar keys %{$lines{$cc}};
+
+        my $upnodes = 0;
+        foreach $router (sort keys %{$lines{$cc}}) {
+            if(${$lines{$cc}}{$router} !~ /hibernating/
+                    and ${$lines{$cc}}{$router} !~ /unresponsive/) {
+                $upnodes++
+            }
+        }
+
+        if($SORTBW) {
+            foreach $router (sort keys %{$lines{$cc}}) {
+                if(${$lines{$cc}}{$router} =~ /unresponsive/) {
+                    $all_u{$router} = ${$lines{$cc}}{$router};
+                } else {
+                    $all_r{$router} = ${$lines{$cc}}{$router};
+                }
+            }
+        } else {
+
+            if(defined $f) {
+                if(not defined $TEXTONLY) {
+                    $response .= sprintf "\n<tr>%s</tr>\n", padded_cell(1);
+                }
+            } else {
+                $f = 1;
+            }
+
+            if(defined $TEXTONLY) {
+                $response .= "\n$cc [$nodes] " . $cc_matches{$cc} . "\n";
+            } else {
+                $response .= "<tr>\n";
+                $response .= "    <td class=\"heading\"><tt>$cc&nbsp;$flag&nbsp;$upnodes/$nodes</tt></td>\n";
+                $response .= sprintf "    %s\n", padded_cell($maxlength + 4, "heading");
+                $response .= sprintf "    %s\n", padded_cell(2);
+                foreach my $PORT (@portslist) {
+                    $response .= sprintf "    %s\n", padded_cell(4);
+                }
+                $response .= "</tr>\n";
+            }
+            $response .= addrouters(\%{$lines{$cc}});
+        }
+    }
+}
+
+if($SORTBW) {
+    $response .= addrouters(\%all_r);
+
+    # add a blank line between responsive and unresponsive routers
+
+    unless($TEXTONLY) {
+        $response .= "<tr>\n";
+        $response .= sprintf "    %s\n", padded_cell($maxname+5);
+        $response .= sprintf "    %s\n", padded_cell($maxlength+4);
+        $response .= sprintf "    %s\n", padded_cell(2);
+        foreach my $PORT (@portslist) {
+            $response .= sprintf "    %s\n", padded_cell(4);
+        }
+        $response .= "</tr>\n";
+    }
+
+    $response .= addrouters(\%all_u);
+}
+
+
+# perform final formatting operations
+
+$num{"v"} = $num{"vur"}+$num{"vhi"}+$num{"vv0"}+$num{"vv1"}+$num{"vv2"}+$num{"vv3"};
+$num{"u"} = $num{"uur"}+$num{"uhi"}+$num{"uv0"}+$num{"uv1"}+$num{"uv2"}+$num{"uv3"};
+
+foreach my $number (sort keys %num) {
+    $num{$number} = sprintf "%4s", $num{$number};
+    $num{$number} =~ s/ /&nbsp;/g;
+}
+
+if($uri ne "") {
+    $uri = "%3F$uri";
+    $uri =~ s/&/%26/g;
+    $uri =~ s/=/%3D/g;
+}
+
+# compose the legend and footer
+
+if(not defined $TEXTONLY) {
+    my @bwheader = {"", "", "", ""};
+    if($BLOSSOM) {
+        $bwheader[0] = "zero throughput";
+        $bwheader[1] = "marginal throughput";
+    } else {
+        $minfast = int($minfast/1000);
+        $bwheader[0] = "zero or marginal throughput";
+        $bwheader[1] = "throughput above eighth octile";
+    }
+    $bwheader[2] = "throughput &gt;= $V2_MINBW kB/s";
+    $bwheader[3] = "throughput &gt;= $V3_MINBW kB/s";
+    $response .= <<EOF
+
+</table>
+
+<p></p>
+
+<table id=legend>
+    <tr>
+        <td><tt><img $P_FLAGS src="$URL_ICONS/$ICON_UR" alt="ur"> = unresponsive node</tt></td>
+        <td class="number"><tt>${num{vur}}</tt></td>
+        <td class="unverifiednumber"><tt>${num{uur}}</tt></td>
+    </tr><tr>
+        <td><tt><img $P_FLAGS src="$URL_ICONS/$ICON_HN" alt="hn"> = hibernating node</tt></td>
+        <td class="number"><tt>${num{vhi}}</tt></td>
+        <td class="unverifiednumber"><tt>${num{uhi}}</tt></td>
+    </tr><tr>
+        <td><tt><img $P_FLAGS src="$URL_ICONS/$ICON_V0" alt="v0"> = $bwheader[0]</tt></td>
+        <td class="number"><tt>${num{vv0}}</tt></td>
+        <td class="unverifiednumber"><tt>${num{uv0}}</tt></td>
+    </tr><tr>
+        <td><tt><img $P_FLAGS src="$URL_ICONS/$ICON_V1" alt="v1"> = $bwheader[1]</tt></td>
+        <td class="number"><tt>${num{vv1}}</tt></td>
+        <td class="unverifiednumber"><tt>${num{uv1}}</tt></td>
+    </tr><tr>
+        <td><tt><img $P_FLAGS src="$URL_ICONS/$ICON_V2" alt="v2"> = $bwheader[2]</tt></td>
+        <td class="number"><tt>${num{vv2}}</tt></td>
+        <td class="unverifiednumber"><tt>${num{uv2}}</tt></td>
+    </tr><tr>
+        <td><tt><img $P_FLAGS src="$URL_ICONS/$ICON_V3" alt="v3"> = $bwheader[3]</tt></td>
+        <td class="number"><tt>$num{vv3}</tt></td>
+        <td class="unverifiednumber"><tt>$num{uv3}</tt></td>
+    </tr>
+    <tr><td><tt>&nbsp;</tt></td></tr>
+EOF
+;
+
+    # provide OS statistics
+
+    foreach my $os (reverse sort by_totals keys %oses) {
+        my $vn = sprintf "%4s", $oses{$os}[0];
+        my $un = sprintf "%4s", $oses{$os}[1];
+        my $f_os = $os;
+        $f_os =~ s/Windows/Windows /;
+        $f_os =~ s/Linux64/Linux 64/;
+
+        $vn =~ s/ /&nbsp;/g;
+        $un =~ s/ /&nbsp;/g;
+
+        $response .= <<EOF
+    <tr>
+        <td><tt>os&nbsp;<img $P_ICONS src="$URL_OSICONS/$os.png" alt="*">&nbsp;$f_os</tt></td>
+        <td class="number"><tt>$vn</tt></td>
+        <td class="unverifiednumber"><tt>$un</tt></td>
+    </tr>
+EOF
+;
+    }
+
+    $response .= <<EOF
+    <tr>
+        <td class="unverified"><tt>* = $UV_TEXT</tt></td>
+    </tr><tr>
+        <td><tt>total nodes</tt></td>
+        <td class="number"><tt>${num{v}}</tt></td>
+        <td class="unverifiednumber"><tt>${num{u}}</tt></td>
+    </tr><tr>
+        <td><tt>directory&nbsp;signature&nbsp;$dsignature</tt></td>
+    </tr><tr>
+        <td><tt>$dpublished</tt></td>
+        <td><tt>&nbsp;UTC</tt></td>
+        <td><tt>&nbsp;&nbsp;&nbsp;&nbsp;</tt></td>
+    </tr><tr>
+        <td><tt>$spublished</tt></td>
+        <td><tt>&nbsp;UTC</tt></td>
+        <td><tt>&nbsp;&nbsp;&nbsp;&nbsp;</tt></td>
+    <tr><td><tt><a href="$URL_SOURCE">source code</a>&nbsp;[<a href="$URL_HOME">official $SYS website</a>]</tt></td></tr>
+</table>
+
+<p><a href="http://validator.w3.org/check?uri=http%3A%2F%2Fserifos.eecs.harvard.edu%2Fcgi-bin%2Fexit.pl$uri";><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
+
+open DNSCACHE, ">$CACHE/$dnscachefile";
+foreach my $ent (sort keys %dns_time) {
+    print DNSCACHE "$dns_time{$ent} $ent $dns_name{$ent}\n";
+}
+close DNSCACHE;
+
+# output the result
+
+print $response;
+exit 0;
+


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

Added: blossom/trunk/flags/19.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/19.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/33.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/33.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/af.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/af.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/al.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/al.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/am.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/am.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/an.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/an.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ao.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ao.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ar.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ar.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/at.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/at.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/au.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/au.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/aw.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/aw.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/az.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/az.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ba.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ba.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bb.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bb.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bd.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bd.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/be.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/be.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bf.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bf.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bg.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bg.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bh.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bh.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bi.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bi.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bj.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bj.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bm.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bm.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bn.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bn.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bo.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bo.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/br.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/br.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bs.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bs.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bt.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bt.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bw.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bw.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/by.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/by.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/bz.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/bz.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ca.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ca.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/cf.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/cf.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/cg.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/cg.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ch.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ch.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ci.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ci.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ck.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ck.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/cl.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/cl.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/cm.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/cm.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/cn.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/cn.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/co.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/co.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/cr.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/cr.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/cu.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/cu.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/cv.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/cv.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/cy.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/cy.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/cz.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/cz.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/de.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/de.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/dk.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/dk.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/dz.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/dz.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ec.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ec.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ee.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ee.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/eg.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/eg.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/er.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/er.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/es.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/es.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/et.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/et.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/fi.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/fi.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/fj.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/fj.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/fo.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/fo.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/fr.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/fr.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ga.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ga.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/gb.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/gb.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ge.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ge.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/gi.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/gi.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/gl.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/gl.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/gp.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/gp.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/gr.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/gr.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/gt.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/gt.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/gu.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/gu.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/gy.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/gy.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/hk.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/hk.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/hr.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/hr.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ht.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ht.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/hu.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/hu.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/id.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/id.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ie.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ie.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/il.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/il.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/in.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/in.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/iq.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/iq.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ir.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ir.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/is.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/is.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/it.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/it.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/jm.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/jm.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/jo.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/jo.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/jp.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/jp.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ke.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ke.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/kg.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/kg.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/kh.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/kh.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ki.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ki.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/kp.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/kp.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/kr.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/kr.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ky.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ky.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/kz.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/kz.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/lb.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/lb.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/lc.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/lc.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/lk.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/lk.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/lt.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/lt.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/lu.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/lu.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/lv.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/lv.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ly.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ly.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ma.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ma.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/mc.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/mc.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/md.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/md.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/mg.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/mg.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/mn.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/mn.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/mo.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/mo.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/mp.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/mp.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ms.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ms.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/mt.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/mt.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/mx.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/mx.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/my.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/my.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/mz.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/mz.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/na.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/na.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/nc.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/nc.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/nf.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/nf.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/nl.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/nl.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/no.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/no.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/np.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/np.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/nr.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/nr.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/nz.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/nz.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/om.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/om.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/pa.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/pa.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/pe.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/pe.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/pf.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/pf.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ph.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ph.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/pk.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/pk.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/pl.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/pl.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/pm.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/pm.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/pr.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/pr.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/pt.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/pt.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/py.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/py.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/qa.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/qa.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ro.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ro.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ru.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ru.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/sa.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/sa.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/sb.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/sb.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/sd.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/sd.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/se.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/se.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/sg.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/sg.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/si.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/si.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/sk.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/sk.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/sl.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/sl.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/sm.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/sm.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/so.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/so.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/sy.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/sy.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/tc.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/tc.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/tg.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/tg.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/th.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/th.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/tn.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/tn.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/to.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/to.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/tp.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/tp.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/tr.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/tr.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/tt.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/tt.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/tv.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/tv.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/tw.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/tw.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/tz.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/tz.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ua.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ua.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ug.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ug.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/us.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/us.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/uy.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/uy.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/va.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/va.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ve.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ve.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/vg.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/vg.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/vi.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/vi.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/vn.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/vn.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ws.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ws.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/ye.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/ye.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/yu.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/yu.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/za.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/za.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/zw.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/zw.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/flags/~~.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/flags/~~.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/icons/bx.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/icons/bx.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/icons/hn.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/icons/hn.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/icons/s0.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/icons/s0.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/icons/s1.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/icons/s1.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/icons/ur.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/icons/ur.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/icons/v0.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/icons/v0.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/icons/v1.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/icons/v1.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/icons/v2.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/icons/v2.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/icons/v3.gif
===================================================================
(Binary files differ)


Property changes on: blossom/trunk/icons/v3.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: blossom/trunk/package-name
===================================================================
--- blossom/trunk/package-name	                        (rev 0)
+++ blossom/trunk/package-name	2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,2 @@
+#!/bin/sh
+perl -nle 'if(/^__version__ = \"(.*)\"/) { print "blossom-$1.tar.bz2" }' blossom.py


Property changes on: blossom/trunk/package-name
___________________________________________________________________
Name: svn:executable
   + *

Added: blossom/trunk/style.css
===================================================================
--- blossom/trunk/style.css	                        (rev 0)
+++ blossom/trunk/style.css	2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,186 @@
+/* $Id: style.css,v 1.6 2005/02/16 19:25:47 goodell Exp */
+
+A:hover {
+   text-decoration: none;
+   color: #000000;
+   background: #FFCC00
+}
+
+A.standard {
+   text-decoration: none;
+   color: #007;
+}
+
+A.unverified {
+   text-decoration: none;
+   color: #C07;
+}
+
+ACRONYM {
+   border-bottom: none;
+}
+
+BODY {
+   background-color: #FFF;
+   color: #000;
+}
+
+P {
+   text-align: justify;
+   font-family: sans, "Lucida Sans", "Sans", "Geneva", sans-serif;
+}
+
+TD, TH, DD, DT, LI {
+   font-family: sans, "Lucida Sans", "Sans", "Geneva", sans-serif;
+}
+
+TH, DT {
+   font-weight: bold;
+}
+
+H1, H2, H3, H4, H5, H6 {
+   font-family: sans, "Lucida Sans", "Sans", "Geneva", sans-serif;
+}
+
+H1 {
+   text-align: left;
+}
+
+H2, H3, H4, H5, H6 {
+   text-align: left;
+}
+
+H2 {
+   border-width: 2px;
+   border-color: #000;
+   border-style: solid;
+   background-color: #828;
+   color: #FFF;
+   padding: 2px 2px 2px 2px;
+}
+
+H3 {
+   border-width: 2px;
+   border-color: #828;
+   border-style: solid;
+   background-color: #CAF; 
+   color: #000;
+   padding: 2px 2px 2px 2px;
+}
+
+DIV.answer {
+   margin: 0 0 0 2em;
+}
+
+SPAN.heading {
+   background-color: #9CF;
+   color: #000;
+   border-width: 1px;
+   padding: 0 0.2em 0 0.2em;
+   border-color: #000;
+   border-style: solid;
+}
+
+SPAN.date {
+   background-color: #FFF;
+   color: #0A0;
+   font-weight: bold;
+}
+
+P.date {
+   background-color: #FFF;
+   color: #A0A;
+   font-weight: bold;
+   margin-bottom: 0;
+}
+
+P.news {
+   margin-top: 0;
+   margin-left: 3em;
+}
+
+P.credit {
+   font-size: smaller;
+   font-style: italic;
+   padding-left: 3px;
+   border-left: 3px solid;
+}
+
+TABLE {
+   border-width: 1px;
+   border-color: #000;
+   border-style: solid;
+   padding: 1px;
+}
+
+TABLE.noborder {
+   border-width: 0px;
+   padding: 1px;
+}
+
+TD.heading {
+   background-color: #AFA;
+   font-weight: bold;
+}
+
+TD.leftentry {
+   text-align: left;
+   background-color: #FC7;
+}
+
+TD.dimleftentry {
+   text-align: left;
+   color: #999;
+   background-color: #FDB;
+}
+
+TD.entry {
+   text-align: center;
+   background-color: #FC7;
+}
+
+TD.dimentry {
+   text-align: center;
+   color: #999;
+   background-color: #FDB;
+}
+
+TD.centered {
+    padding: 0px;
+    text-align: center;
+}
+
+TD.boldentry {
+   background-color: #FD9;
+   font-weight: bold;
+}
+
+TD.boldnormal {
+   font-weight: bold;
+}
+
+TD.closedstream {
+   color: #C00;
+}
+
+TD.failedstream {
+   background-color: #F99;
+   color: #000;
+}
+
+TD.unverified {
+   color: #C00;
+}
+
+TD.number {
+   text-align: right;
+}
+
+TD.unverifiednumber {
+   text-align: right;
+   color: #C00;
+}
+
+IMG {
+   border-width: 0px;
+}

Added: blossom/trunk/tor-resolve-server.pl
===================================================================
--- blossom/trunk/tor-resolve-server.pl	                        (rev 0)
+++ blossom/trunk/tor-resolve-server.pl	2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,68 @@
+#!/usr/bin/perl
+# tor-resolve-server.pl - dns server that does queries with tor-resolve
+# Copyright (C) 2005 Timo Lindfors <timo.lindfors@xxxxxx>
+#
+#This program is free software; you can redistribute it and/or modify
+#it under the terms of the GNU General Public License as published by
+#the Free Software Foundation; either version 2 of the License, or
+#(at your option) any later version.
+
+#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
+
+# sudo iptables -t nat -A PREROUTING -p tcp -d 192.168.0.4 --dport 53 -j DNAT --to-destination 192.168.0.4:5353
+# to test the setup: dig @localhost -p 5353 www.google.com
+
+use strict;
+use warnings;
+BEGIN { eval { require Net::DNS; }; if($@) {die "Please apt-get install libnet-dns-perl\n";} }
+use Net::DNS;
+use Net::DNS::Nameserver;
+
+sub parse_ptr {
+    my ($qname) = @_;
+    if ($qname =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\.in-addr.arpa$/) {
+	return "$4.$3.$2.$1"; # reverse the order of octets
+    }
+    return;
+}
+sub reply_handler {
+    my ($qname, $qclass, $qtype, $peerhost) = @_;
+    my ($rcode, @ans, @auth, @add);
+
+#    print "DEBUG: qname = $qname, qclass = $qclass, qtype = $qtype, peerhost = $peerhost\n";
+    if ($qtype eq "A") {
+	if ($qname =~ /^[0-9A-Za-z\.\-]+$/) {
+	    my $ttl = 3600;
+	    my $rdata = `tor-resolve $qname`;
+#	    print "DEBUG2: rdata = $rdata\n";
+	    push @ans, Net::DNS::RR->new("$qname $ttl $qclass $qtype $rdata");
+	    $rcode = "NOERROR";
+	} else {
+	    print "\"$qname\" failed sanity check\n";
+	}
+    } else {
+	print "Received query of type \"$qtype\" but tor-resolve can only handle \"A\".\n";
+        $rcode = "NXDOMAIN";
+    }
+
+    # mark the answer as authoritive (by setting the 'aa' flag
+    return ($rcode, \@ans, \@auth, \@add, { aa => 1 });
+}
+
+my $ns = Net::DNS::Nameserver->new(
+    LocalAddr    => "127.0.0.1",
+    LocalPort    => 5353,
+    ReplyHandler => \&reply_handler,
+    Verbose      => 1,
+				   ) || die "couldn't create nameserver object\n";
+
+
+$ns->main_loop;
+


Property changes on: blossom/trunk/tor-resolve-server.pl
___________________________________________________________________
Name: svn:executable
   + *