[or-cvs] r10838: put geoff goodell's tarball into svn as a 'blossom' module (/ blossom blossom/trunk blossom/trunk/flags blossom/trunk/icons)
arma at seul.org
arma at seul.org
Mon Jul 16 07:09:50 UTC 2007
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/ / /g;
+
+ $r = <<EOF
+
+<tr>
+ <td$bb><tt>$icon $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> <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/ / /;
+ (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\"> $cc </td>\n";
+ $response .= " <td class=\"number\">$nn</td>\n";
+ $response .= "</tr>\n";
+ }
+ }
+
+ $response .= <<EOF
+
+</table>
+</form>
+
+<p>
+ [<a href="$URL_SOURCE">source code</a>]
+ [<a href="$URL_HOME">Blossom home 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> <img %s src=\"%s\"> %s </tt>" \
+ % (IMG_SIZE, ICON_BLANK, message)
+ else:
+ return "<tt> <input type=\"image\" name=\"s_smite\" value=\"%s\" %s src=\"%s\" alt=\"ss\"> %s </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\"> " % (IMG_SIZE, URL_FLAGS, ccs)
+ else:
+ icon = "<img %s src=\"%s\"> " % (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> %s </tt></td>
+ <td%s><tt> %s </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> %s %s %s</tt></td>
+ <td class="%s" style="text-align:right"><tt> %s </tt></td>
+ <td class="%s"><tt> %s </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> <img src="%s"> <img src="%s"> </tt></td>
+ <td class="dimleftentry"></td>
+ <td class="dimleftentry"><tt> [unattached] </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 .= " ";
+ }
+ $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/&/&/g;
+$uri_addr =~ s/&/&/g;
+$uri_sort =~ s/&/&/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> $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> $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/ / /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/ / /g if not $TEXTONLY;
+ $host = $wrapper . $host;
+ if($DISPLAY_ADDR) {
+ $host .= " $version";
+ $host =~ s/ / /g if not $TEXTONLY;
+ }
+ } elsif(not $TEXTONLY) {
+ $slen = 3;
+ $t = $netname;
+ $wrapper = " [<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/ / /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&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 %s", make_flag($cc), $router_td;
+ } else {
+ if($cc =~ /US|CA/) {
+ $t = parsewhois("stateprov", " ", \@lines);
+ $t = "~~" unless $t =~ /^[A-Z][A-Z]$/;
+ $router_td = "$t $router_td ";
+ } else {
+ $router_td = " $router_td ";
+ }
+ }
+ $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 $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> $router_elt </tt></td>\n";
+ ${$lines{"~~"}}{$router_elt} .= " <td><tt>$icon $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 $flag $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/ / /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 >= $V2_MINBW kB/s";
+ $bwheader[3] = "throughput >= $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> </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/ / /g;
+ $un =~ s/ / /g;
+
+ $response .= <<EOF
+ <tr>
+ <td><tt>os <img $P_ICONS src="$URL_OSICONS/$os.png" alt="*"> $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 signature $dsignature</tt></td>
+ </tr><tr>
+ <td><tt>$dpublished</tt></td>
+ <td><tt> UTC</tt></td>
+ <td><tt> </tt></td>
+ </tr><tr>
+ <td><tt>$spublished</tt></td>
+ <td><tt> UTC</tt></td>
+ <td><tt> </tt></td>
+ <tr><td><tt><a href="$URL_SOURCE">source code</a> [<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 at iki.fi>
+#
+#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
+ *
More information about the tor-commits
mailing list