[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[or-cvs] r10838: put geoff goodell's tarball into svn as a 'blossom' module (/ blossom blossom/trunk blossom/trunk/flags blossom/trunk/icons)
Author: arma
Date: 2007-07-16 03:09:49 -0400 (Mon, 16 Jul 2007)
New Revision: 10838
Added:
blossom/
blossom/branches/
blossom/tags/
blossom/trunk/
blossom/trunk/Makefile
blossom/trunk/TorCtl.py
blossom/trunk/TorCtl0.py
blossom/trunk/TorCtl1.py
blossom/trunk/blossom.pl
blossom/trunk/blossom.py
blossom/trunk/country-codes.txt
blossom/trunk/exit.pl
blossom/trunk/flags/
blossom/trunk/flags/19.gif
blossom/trunk/flags/33.gif
blossom/trunk/flags/af.gif
blossom/trunk/flags/al.gif
blossom/trunk/flags/am.gif
blossom/trunk/flags/an.gif
blossom/trunk/flags/ao.gif
blossom/trunk/flags/ar.gif
blossom/trunk/flags/at.gif
blossom/trunk/flags/au.gif
blossom/trunk/flags/aw.gif
blossom/trunk/flags/az.gif
blossom/trunk/flags/ba.gif
blossom/trunk/flags/bb.gif
blossom/trunk/flags/bd.gif
blossom/trunk/flags/be.gif
blossom/trunk/flags/bf.gif
blossom/trunk/flags/bg.gif
blossom/trunk/flags/bh.gif
blossom/trunk/flags/bi.gif
blossom/trunk/flags/bj.gif
blossom/trunk/flags/bm.gif
blossom/trunk/flags/bn.gif
blossom/trunk/flags/bo.gif
blossom/trunk/flags/br.gif
blossom/trunk/flags/bs.gif
blossom/trunk/flags/bt.gif
blossom/trunk/flags/bw.gif
blossom/trunk/flags/by.gif
blossom/trunk/flags/bz.gif
blossom/trunk/flags/ca.gif
blossom/trunk/flags/cf.gif
blossom/trunk/flags/cg.gif
blossom/trunk/flags/ch.gif
blossom/trunk/flags/ci.gif
blossom/trunk/flags/ck.gif
blossom/trunk/flags/cl.gif
blossom/trunk/flags/cm.gif
blossom/trunk/flags/cn.gif
blossom/trunk/flags/co.gif
blossom/trunk/flags/cr.gif
blossom/trunk/flags/cu.gif
blossom/trunk/flags/cv.gif
blossom/trunk/flags/cy.gif
blossom/trunk/flags/cz.gif
blossom/trunk/flags/de.gif
blossom/trunk/flags/dk.gif
blossom/trunk/flags/dz.gif
blossom/trunk/flags/ec.gif
blossom/trunk/flags/ee.gif
blossom/trunk/flags/eg.gif
blossom/trunk/flags/er.gif
blossom/trunk/flags/es.gif
blossom/trunk/flags/et.gif
blossom/trunk/flags/fi.gif
blossom/trunk/flags/fj.gif
blossom/trunk/flags/fo.gif
blossom/trunk/flags/fr.gif
blossom/trunk/flags/ga.gif
blossom/trunk/flags/gb.gif
blossom/trunk/flags/ge.gif
blossom/trunk/flags/gi.gif
blossom/trunk/flags/gl.gif
blossom/trunk/flags/gp.gif
blossom/trunk/flags/gr.gif
blossom/trunk/flags/gt.gif
blossom/trunk/flags/gu.gif
blossom/trunk/flags/gy.gif
blossom/trunk/flags/hk.gif
blossom/trunk/flags/hr.gif
blossom/trunk/flags/ht.gif
blossom/trunk/flags/hu.gif
blossom/trunk/flags/id.gif
blossom/trunk/flags/ie.gif
blossom/trunk/flags/il.gif
blossom/trunk/flags/in.gif
blossom/trunk/flags/iq.gif
blossom/trunk/flags/ir.gif
blossom/trunk/flags/is.gif
blossom/trunk/flags/it.gif
blossom/trunk/flags/jm.gif
blossom/trunk/flags/jo.gif
blossom/trunk/flags/jp.gif
blossom/trunk/flags/ke.gif
blossom/trunk/flags/kg.gif
blossom/trunk/flags/kh.gif
blossom/trunk/flags/ki.gif
blossom/trunk/flags/kp.gif
blossom/trunk/flags/kr.gif
blossom/trunk/flags/ky.gif
blossom/trunk/flags/kz.gif
blossom/trunk/flags/lb.gif
blossom/trunk/flags/lc.gif
blossom/trunk/flags/lk.gif
blossom/trunk/flags/lt.gif
blossom/trunk/flags/lu.gif
blossom/trunk/flags/lv.gif
blossom/trunk/flags/ly.gif
blossom/trunk/flags/ma.gif
blossom/trunk/flags/mc.gif
blossom/trunk/flags/md.gif
blossom/trunk/flags/mg.gif
blossom/trunk/flags/mn.gif
blossom/trunk/flags/mo.gif
blossom/trunk/flags/mp.gif
blossom/trunk/flags/ms.gif
blossom/trunk/flags/mt.gif
blossom/trunk/flags/mx.gif
blossom/trunk/flags/my.gif
blossom/trunk/flags/mz.gif
blossom/trunk/flags/na.gif
blossom/trunk/flags/nc.gif
blossom/trunk/flags/nf.gif
blossom/trunk/flags/nl.gif
blossom/trunk/flags/no.gif
blossom/trunk/flags/np.gif
blossom/trunk/flags/nr.gif
blossom/trunk/flags/nz.gif
blossom/trunk/flags/om.gif
blossom/trunk/flags/pa.gif
blossom/trunk/flags/pe.gif
blossom/trunk/flags/pf.gif
blossom/trunk/flags/ph.gif
blossom/trunk/flags/pk.gif
blossom/trunk/flags/pl.gif
blossom/trunk/flags/pm.gif
blossom/trunk/flags/pr.gif
blossom/trunk/flags/pt.gif
blossom/trunk/flags/py.gif
blossom/trunk/flags/qa.gif
blossom/trunk/flags/ro.gif
blossom/trunk/flags/ru.gif
blossom/trunk/flags/sa.gif
blossom/trunk/flags/sb.gif
blossom/trunk/flags/sd.gif
blossom/trunk/flags/se.gif
blossom/trunk/flags/sg.gif
blossom/trunk/flags/si.gif
blossom/trunk/flags/sk.gif
blossom/trunk/flags/sl.gif
blossom/trunk/flags/sm.gif
blossom/trunk/flags/so.gif
blossom/trunk/flags/sy.gif
blossom/trunk/flags/tc.gif
blossom/trunk/flags/tg.gif
blossom/trunk/flags/th.gif
blossom/trunk/flags/tn.gif
blossom/trunk/flags/to.gif
blossom/trunk/flags/tp.gif
blossom/trunk/flags/tr.gif
blossom/trunk/flags/tt.gif
blossom/trunk/flags/tv.gif
blossom/trunk/flags/tw.gif
blossom/trunk/flags/tz.gif
blossom/trunk/flags/ua.gif
blossom/trunk/flags/ug.gif
blossom/trunk/flags/us.gif
blossom/trunk/flags/uy.gif
blossom/trunk/flags/va.gif
blossom/trunk/flags/ve.gif
blossom/trunk/flags/vg.gif
blossom/trunk/flags/vi.gif
blossom/trunk/flags/vn.gif
blossom/trunk/flags/ws.gif
blossom/trunk/flags/ye.gif
blossom/trunk/flags/yu.gif
blossom/trunk/flags/za.gif
blossom/trunk/flags/zw.gif
blossom/trunk/flags/~~.gif
blossom/trunk/icons/
blossom/trunk/icons/bx.gif
blossom/trunk/icons/hn.gif
blossom/trunk/icons/s0.gif
blossom/trunk/icons/s1.gif
blossom/trunk/icons/ur.gif
blossom/trunk/icons/v0.gif
blossom/trunk/icons/v1.gif
blossom/trunk/icons/v2.gif
blossom/trunk/icons/v3.gif
blossom/trunk/package-name
blossom/trunk/style.css
blossom/trunk/tor-resolve-server.pl
Log:
put geoff goodell's tarball into svn as a 'blossom' module
Added: blossom/trunk/Makefile
===================================================================
--- blossom/trunk/Makefile (rev 0)
+++ blossom/trunk/Makefile 2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,24 @@
+TARGET_DIR=~/.blossom
+
+all:
+ rm -rf ${TARGET_DIR}
+ mkdir ${TARGET_DIR}
+ cp -r ./country-codes.txt ./flags ./icons ./style.css ${TARGET_DIR}
+
+dist: clean
+ mkdir blossom
+ mkdir blossom/flags
+ mkdir blossom/icons
+ cp *.* blossom
+ rm -f blossom/*.bz2
+ cp Makefile blossom
+ cp package-name blossom
+ cp flags/*.* blossom/flags
+ cp icons/*.* blossom/icons
+ tar cvjf `./package-name` blossom
+ /bin/rm -rf blossom
+
+clean:
+ /bin/rm -rf blossom *~ *.pyc
+
+FORCE:
Added: blossom/trunk/TorCtl.py
===================================================================
--- blossom/trunk/TorCtl.py (rev 0)
+++ blossom/trunk/TorCtl.py 2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,536 @@
+#!/usr/bin/python
+# TorCtl.py -- Python module to interface with Tor Control interface.
+# Copyright 2005 Nick Mathewson -- See LICENSE for licensing information.
+#$Id: TorCtl.py,v 1.13 2005/11/19 19:42:31 nickm Exp $
+
+"""
+TorCtl -- Library to control Tor processes. See TorCtlDemo.py for example use.
+"""
+
+import os
+import re
+import struct
+import sys
+import threading
+import Queue
+
+class TorCtlError(Exception):
+ "Generic error raised by TorControl code."
+ pass
+
+class TorCtlClosed(TorCtlError):
+ "Raised when the controller connection is closed by Tor (not by us.)"
+ pass
+
+class ProtocolError(TorCtlError):
+ "Raised on violations in Tor controller protocol"
+ pass
+
+class ErrorReply(TorCtlError):
+ "Raised when Tor controller returns an error"
+ pass
+
+class EventHandler:
+ """An 'EventHandler' wraps callbacks for the events Tor can return."""
+ def __init__(self):
+ """Create a new EventHandler."""
+ from TorCtl0 import EVENT_TYPE
+ self._map0 = {
+ EVENT_TYPE.CIRCSTATUS : self.circ_status,
+ EVENT_TYPE.STREAMSTATUS : self.stream_status,
+ EVENT_TYPE.ORCONNSTATUS : self.or_conn_status,
+ EVENT_TYPE.BANDWIDTH : self.bandwidth,
+ EVENT_TYPE.NEWDESC : self.new_desc,
+ EVENT_TYPE.DEBUG_MSG : self.msg,
+ EVENT_TYPE.INFO_MSG : self.msg,
+ EVENT_TYPE.NOTICE_MSG : self.msg,
+ EVENT_TYPE.WARN_MSG : self.msg,
+ EVENT_TYPE.ERR_MSG : self.msg,
+ }
+ self._map1 = {
+ "CIRC" : self.circ_status,
+ "STREAM" : self.stream_status,
+ "ORCONN" : self.or_conn_status,
+ "BW" : self.bandwidth,
+ "DEBUG" : self.msg,
+ "INFO" : self.msg,
+ "NOTICE" : self.msg,
+ "WARN" : self.msg,
+ "ERR" : self.msg,
+ "NEWDESC" : self.new_desc,
+ "ADDRMAP" : self.address_mapped
+ }
+
+ def handle0(self, evbody):
+ """Dispatcher: called from Connection when an event is received."""
+ evtype, args = self.decode0(evbody)
+ self._map0.get(evtype, self.unknown_event)(evtype, *args)
+
+ def decode0(self, body):
+ """Unpack an event message into a type/arguments-tuple tuple."""
+ if len(body)<2:
+ raise ProtocolError("EVENT body too short.")
+ evtype, = struct.unpack("!H", body[:2])
+ body = body[2:]
+ if evtype == EVENT_TYPE.CIRCSTATUS:
+ if len(body)<5:
+ raise ProtocolError("CIRCUITSTATUS event too short.")
+ status,ident = struct.unpack("!BL", body[:5])
+ path = _unterminate(body[5:]).split(",")
+ args = status, ident, path
+ elif evtype == EVENT_TYPE.STREAMSTATUS:
+ if len(body)<5:
+ raise ProtocolError("STREAMSTATUS event too short.")
+ status,ident = struct.unpack("!BL", body[:5])
+ target = _unterminate(body[5:])
+ args = status, ident, target
+ elif evtype == EVENT_TYPE.ORCONNSTATUS:
+ if len(body)<2:
+ raise ProtocolError("ORCONNSTATUS event too short.")
+ status = ord(body[0])
+ target = _unterminate(body[1:])
+ args = status, target
+ elif evtype == EVENT_TYPE.BANDWIDTH:
+ if len(body)<8:
+ raise ProtocolError("BANDWIDTH event too short.")
+ read, written = struct.unpack("!LL",body[:8])
+ args = read, written
+ elif evtype == EVENT_TYPE.OBSOLETE_LOG:
+ args = (_unterminate(body),)
+ elif evtype == EVENT_TYPE.NEWDESC:
+ args = (_unterminate(body).split(","),)
+ elif EVENT_TYPE.DEBUG_MSG <= evtype <= EVENT_TYPE.ERR_MSG:
+ args = (EVENT_TYPE.nameOf[evtype], _unterminate(body))
+ else:
+ args = (body,)
+
+ return evtype, args
+
+ def handle1(self, lines):
+ """Dispatcher: called from Connection when an event is received."""
+ for code, msg, data in lines:
+ evtype, args = self.decode1(msg)
+ self._map1.get(evtype, self.unknown_event)(evtype, *args)
+
+ def decode1(self, body):
+ """Unpack an event message into a type/arguments-tuple tuple."""
+ if " " in body:
+ evtype,body = body.split(" ",1)
+ else:
+ evtype,body = body,""
+ evtype = evtype.upper()
+ if evtype == "CIRC":
+ m = re.match(r"(\S+)\s+(\S+)(\s\S+)?", body)
+ if not m:
+ raise ProtocolError("CIRC event misformatted.")
+ status,ident,path = m.groups()
+ if path:
+ path = path.strip().split(",")
+ else:
+ path = []
+ args = status, ident, path
+ elif evtype == "STREAM":
+ m = re.match(r"(\S+)\s+(\S+)\s+(\S+)\s+(\S+)", body)
+ if not m:
+ raise ProtocolError("STREAM event misformatted.")
+ ident,status,circ,target = m.groups()
+ args = status, ident, target, circ
+ elif evtype == "ORCONN":
+ m = re.match(r"(\S+)\s+(\S+)", body)
+ if not m:
+ raise ProtocolError("ORCONN event misformatted.")
+ target, status = m.groups()
+ args = status, target
+ elif evtype == "BW":
+ m = re.match(r"(\d+)\s+(\d+)", body)
+ if not m:
+ raise ProtocolError("BANDWIDTH event misformatted.")
+ read, written = map(long, m.groups())
+ args = read, written
+ elif evtype in ("DEBUG", "INFO", "NOTICE", "WARN", "ERR"):
+ args = evtype, body
+ elif evtype == "NEWDESC":
+ args = ((" ".split(body)),)
+ elif evtype == "ADDRMAP":
+ m = re.match(r'(\S+)\s+(\S+)\s+(\"[^"]+\"|\w+)')
+ if not m:
+ raise ProtocolError("BANDWIDTH event misformatted.")
+ fromaddr, toaddr, when = m.groups()
+ if when.upper() == "NEVER":
+ when = None
+ else:
+ when = time.localtime(
+ time.strptime(when[1:-1], "%Y-%m-%d %H:%M:%S"))
+ args = fromaddr, toaddr, when
+ else:
+ args = (body,)
+
+ return evtype, args
+
+ def unknown_event(self, eventtype, evtype, *args):
+ """Called when we get an event type we don't recognize. This
+ is almost alwyas an error.
+ """
+ raise NotImplemented
+
+ def circ_status(self, eventtype, status, circID, path):
+ """Called when a circuit status changes if listening to CIRCSTATUS
+ events. 'status' is a member of CIRC_STATUS; circID is a numeric
+ circuit ID, and 'path' is the circuit's path so far as a list of
+ names.
+ """
+ raise NotImplemented
+
+ def stream_status(self, eventtype, status, streamID, target, circID="0"):
+ """Called when a stream status changes if listening to STREAMSTATUS
+ events. 'status' is a member of STREAM_STATUS; streamID is a
+ numeric stream ID, and 'target' is the destination of the stream.
+ """
+ raise NotImplemented
+
+ def or_conn_status(self, eventtype, status, target):
+ """Called when an OR connection's status changes if listening to
+ ORCONNSTATUS events. 'status' is a member of OR_CONN_STATUS; target
+ is the OR in question.
+ """
+ raise NotImplemented
+
+ def bandwidth(self, eventtype, read, written):
+ """Called once a second if listening to BANDWIDTH events. 'read' is
+ the number of bytes read; 'written' is the number of bytes written.
+ """
+ raise NotImplemented
+
+ def new_desc(self, eventtype, identities):
+ """Called when Tor learns a new server descriptor if listenting to
+ NEWDESC events.
+ """
+ raise NotImplemented
+
+ def msg(self, eventtype, severity, message):
+ """Called when a log message of a given severity arrives if listening
+ to INFO_MSG, NOTICE_MSG, WARN_MSG, or ERR_MSG events."""
+ raise NotImplemented
+
+ def address_mapped(self, eventtype, fromAddr, toAddr, expiry=None):
+ """Called when Tor adds a mapping for an address if listening
+ to ADDRESSMAPPED events.
+ """
+ raise NotImplemented
+
+class _ConnectionBase:
+ def __init__(self):
+ self._s = None
+ self._handler = None
+ self._handleFn = None
+ self._sendLock = threading.RLock()
+ self._queue = Queue.Queue()
+ self._thread = None
+ self._closedEx = None
+ self._closed = 0
+ self._closeHandler = None
+ self._eventThread = None
+ self._eventQueue = Queue.Queue()
+
+ def set_event_handler(self, handler):
+ """Cause future events from the Tor process to be sent to 'handler'.
+ """
+ raise NotImplemented
+
+ def set_close_handler(self, handler):
+ """Call 'handler' when the Tor process has closed its connection or
+ given us an exception. If we close normally, no arguments are
+ provided; otherwise, it will be called with an exception as its
+ argument.
+ """
+ self._closeHandler = handler
+
+ def close(self):
+ """Shut down this controller connection"""
+ self._sendLock.acquire()
+ try:
+ self._queue.put("CLOSE")
+ self._eventQueue.put("CLOSE")
+ self._s.close()
+ self._s = None
+ self._closed = 1
+ finally:
+ self._sendLock.release()
+
+ def _read_reply(self):
+ """DOCDOC"""
+ raise NotImplementd
+
+ def launch_thread(self, daemon=1):
+ """Launch a background thread to handle messages from the Tor process."""
+ assert self._thread is None
+ t = threading.Thread(target=self._loop)
+ if daemon:
+ t.setDaemon(daemon)
+ t.start()
+ self._thread = t
+ t = threading.Thread(target=self._eventLoop)
+ if daemon:
+ t.setDaemon(daemon)
+ t.start()
+ self._eventThread = t
+ return self._thread
+
+ def _loop(self):
+ """Main subthread loop: Read commands from Tor, and handle them either
+ as events or as responses to other commands.
+ """
+ while 1:
+ ex = None
+ try:
+ isEvent, reply = self._read_reply()
+ except:
+ self._err(sys.exc_info())
+ return
+
+ if isEvent:
+ if self._handler is not None:
+ self._eventQueue.put(reply)
+ else:
+ cb = self._queue.get()
+ cb(reply)
+
+ def _err(self, (tp, ex, tb), fromEventLoop=0):
+ """DOCDOC"""
+ if self._s:
+ try:
+ self.close()
+ except:
+ pass
+ self._sendLock.acquire()
+ try:
+ self._closedEx = ex
+ self._closed = 1
+ finally:
+ self._sendLock.release()
+ while 1:
+ try:
+ cb = self._queue.get(timeout=0)
+ if cb != "CLOSE":
+ cb("EXCEPTION")
+ except Queue.Empty:
+ break
+ if self._closeHandler is not None:
+ self._closeHandler(ex)
+ return
+
+ def _eventLoop(self):
+ """DOCDOC"""
+ while 1:
+ reply = self._eventQueue.get()
+ if reply == "CLOSE":
+ return
+ try:
+ self._handleFn(reply)
+ except:
+ self._err(sys.exc_info(), 1)
+ return
+
+ def _sendImpl(self, sendFn, msg):
+ """DOCDOC"""
+ if self._thread is None:
+ self.launch_thread(1)
+ # This condition will get notified when we've got a result...
+ condition = threading.Condition()
+ # Here's where the result goes...
+ result = []
+
+ if self._closedEx is not None:
+ raise self._closedEx
+ elif self._closed:
+ raise TorCtl.TorCtlClosed()
+
+ def cb(reply,condition=condition,result=result):
+ condition.acquire()
+ try:
+ result.append(reply)
+ condition.notify()
+ finally:
+ condition.release()
+
+ # Sends a message to Tor...
+ self._sendLock.acquire()
+ try:
+ self._queue.put(cb)
+ sendFn(msg)
+ finally:
+ self._sendLock.release()
+
+ # Now wait till the answer is in...
+ condition.acquire()
+ try:
+ while not result:
+ condition.wait()
+ finally:
+ condition.release()
+
+ # ...And handle the answer appropriately.
+ assert len(result) == 1
+ reply = result[0]
+ if reply == "EXCEPTION":
+ raise self._closedEx
+
+ return reply
+
+class DebugEventHandler(EventHandler):
+ """Trivial event handler: dumps all events to stdout."""
+ def __init__(self, out=None):
+ if out is None:
+ out = sys.stdout
+ self._out = out
+
+ def handle0(self, body):
+ evtype, args = self.decode0(body)
+ print >>self._out,EVENT_TYPE.nameOf[evtype],args
+
+ def handle1(self, lines):
+ for code, msg, data in lines:
+ print >>self._out, msg
+
+def detectVersion(s):
+ """Helper: sends a trial command to Tor to tell whether it's running
+ the first or second version of the control protocol.
+ """
+ s.sendall("\x00\x00\r\n")
+ m = s.recv(4)
+ v0len, v0type = struct.unpack("!HH", m)
+ if v0type == '\x00\x00':
+ s.recv(v0len)
+ return 0
+ if '\n' not in m:
+ while 1:
+ c = s.recv(1)
+ if c == '\n':
+ break
+ return 1
+
+def parseHostAndPort(h):
+ """Given a string of the form 'address:port' or 'address' or
+ 'port' or '', return a two-tuple of (address, port)
+ """
+ host, port = "localhost", 9100
+ if ":" in h:
+ i = h.index(":")
+ host = h[:i]
+ try:
+ port = int(h[i+1:])
+ except ValueError:
+ print "Bad hostname %r"%h
+ sys.exit(1)
+ elif h:
+ try:
+ port = int(h)
+ except ValueError:
+ host = h
+
+ return host, port
+
+def get_connection(sock):
+ """Given a socket attached to a Tor control port, detect the version of Tor
+ and return an appropriate 'Connection' object."""
+ v = detectVersion(sock)
+ if v == 0:
+ import TorCtl0
+ return TorCtl0.Connection(sock)
+ else:
+ import TorCtl1
+ return TorCtl1.Connection(sock)
+
+def secret_to_key(secret, s2k_specifier):
+ """Used to generate a hashed password string. DOCDOC."""
+ c = ord(s2k_specifier[8])
+ EXPBIAS = 6
+ count = (16+(c&15)) << ((c>>4) + EXPBIAS)
+
+ d = sha.new()
+ tmp = s2k_specifier[:8]+secret
+ slen = len(tmp)
+ while count:
+ if count > slen:
+ d.update(tmp)
+ count -= slen
+ else:
+ d.update(tmp[:count])
+ count = 0
+ return d.digest()
+
+def urandom_rng(n):
+ """Try to read some entropy from the platform entropy source."""
+ f = open('/dev/urandom', 'rb')
+ try:
+ return f.read(n)
+ finally:
+ f.close()
+
+def s2k_gen(secret, rng=None):
+ """DOCDOC"""
+ if rng is None:
+ if hasattr(os, "urandom"):
+ rng = os.urandom
+ else:
+ rng = urandom_rng
+ spec = "%s%s"%(rng(8), chr(96))
+ return "16:%s"%(
+ binascii.b2a_hex(spec + secret_to_key(secret, spec)))
+
+def s2k_check(secret, k):
+ """DOCDOC"""
+ assert k[:3] == "16:"
+
+ k = binascii.a2b_hex(k[3:])
+ return secret_to_key(secret, k[:9]) == k[9:]
+
+def run_example(host,port):
+ print "host is %s:%d"%(host,port)
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect((host,port))
+ c = Connection(s)
+ c.set_event_handler(DebugEventHandler())
+ th = c.launchThread()
+ c.authenticate()
+ print "nick",`c.get_option("nickname")`
+ print c.get_option("DirFetchPeriod\n")
+ print `c.get_info("version")`
+ #print `c.get_info("desc/name/moria1")`
+ print `c.get_info("network-status")`
+ print `c.get_info("addr-mappings/all")`
+ print `c.get_info("addr-mappings/config")`
+ print `c.get_info("addr-mappings/cache")`
+ print `c.get_info("addr-mappings/control")`
+ print `c.map_address([("0.0.0.0", "Foobar.com"),
+ ("1.2.3.4", "foobaz.com"),
+ ("frebnitz.com", "5.6.7.8"),
+ (".", "abacinator.onion")])`
+ print `c.extend_circuit(0,["moria1"])`
+ try:
+ print `c.extend_circuit(0,[""])`
+ except ErrorReply:
+ print "got error. good."
+ #send_signal(s,1)
+ #save_conf(s)
+
+ #set_option(s,"1")
+ #set_option(s,"bandwidthburstbytes 100000")
+ #set_option(s,"runasdaemon 1")
+ #set_events(s,[EVENT_TYPE.WARN])
+ c.set_events([EVENT_TYPE.ORCONNSTATUS, EVENT_TYPE.STREAMSTATUS,
+ EVENT_TYPE.CIRCSTATUS, EVENT_TYPE.INFO_MSG,
+ EVENT_TYPE.BANDWIDTH])
+
+ th.join()
+ return
+
+if __name__ == '__main__':
+ if len(sys.argv) > 2:
+ print "Syntax: TorControl.py torhost:torport"
+ sys.exit(0)
+ else:
+ sys.argv.append("localhost:9051")
+ sh,sp = parseHostAndPort(sys.argv[1])
+ run_example(sh,sp)
+
Property changes on: blossom/trunk/TorCtl.py
___________________________________________________________________
Name: svn:executable
+ *
Added: blossom/trunk/TorCtl0.py
===================================================================
--- blossom/trunk/TorCtl0.py (rev 0)
+++ blossom/trunk/TorCtl0.py 2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,446 @@
+#!/usr/bin/python
+# TorCtl.py -- Python module to interface with Tor Control interface.
+# Copyright 2005 Nick Mathewson -- See LICENSE for licensing information.
+#$Id: TorCtl0.py,v 1.7 2005/11/19 19:42:31 nickm Exp $
+
+"""
+TorCtl0 -- Library to control Tor processes. See TorCtlDemo.py for example use.
+"""
+
+import binascii
+import os
+import sha
+import socket
+import struct
+import sys
+import TorCtl
+
+__all__ = [
+ "MSG_TYPE", "EVENT_TYPE", "CIRC_STATUS", "STREAM_STATUS",
+ "OR_CONN_STATUS", "SIGNAL", "ERR_CODES",
+ "TorCtlError", "ProtocolError", "ErrorReply", "Connection", "EventHandler",
+ ]
+
+class _Enum:
+ # Helper: define an ordered dense name-to-number 1-1 mapping.
+ def __init__(self, start, names):
+ self.nameOf = {}
+ idx = start
+ for name in names:
+ setattr(self,name,idx)
+ self.nameOf[idx] = name
+ idx += 1
+
+class _Enum2:
+ # Helper: define an ordered sparse name-to-number 1-1 mapping.
+ def __init__(self, **args):
+ self.__dict__.update(args)
+ self.nameOf = {}
+ for k,v in args.items():
+ self.nameOf[v] = k
+
+# Message types that client or server can send.
+MSG_TYPE = _Enum(0x0000,
+ ["ERROR",
+ "DONE",
+ "SETCONF",
+ "GETCONF",
+ "CONFVALUE",
+ "SETEVENTS",
+ "EVENT",
+ "AUTH",
+ "SAVECONF",
+ "SIGNAL",
+ "MAPADDRESS",
+ "GETINFO",
+ "INFOVALUE",
+ "EXTENDCIRCUIT",
+ "ATTACHSTREAM",
+ "POSTDESCRIPTOR",
+ "FRAGMENTHEADER",
+ "FRAGMENT",
+ "REDIRECTSTREAM",
+ "CLOSESTREAM",
+ "CLOSECIRCUIT",
+ ])
+
+# Make sure that the enumeration code is working.
+assert MSG_TYPE.SAVECONF == 0x0008
+assert MSG_TYPE.CLOSECIRCUIT == 0x0014
+
+# Types of "EVENT" message.
+EVENT_TYPE = _Enum(0x0001,
+ ["CIRCSTATUS",
+ "STREAMSTATUS",
+ "ORCONNSTATUS",
+ "BANDWIDTH",
+ "OBSOLETE_LOG",
+ "NEWDESC",
+ "DEBUG_MSG",
+ "INFO_MSG",
+ "NOTICE_MSG",
+ "WARN_MSG",
+ "ERR_MSG",
+ ])
+
+assert EVENT_TYPE.ERR_MSG == 0x000B
+assert EVENT_TYPE.OBSOLETE_LOG == 0x0005
+
+# Status codes for "CIRCSTATUS" events.
+CIRC_STATUS = _Enum(0x00,
+ ["LAUNCHED",
+ "BUILT",
+ "EXTENDED",
+ "FAILED",
+ "CLOSED"])
+
+# Status codes for "STREAMSTATUS" events
+STREAM_STATUS = _Enum(0x00,
+ ["SENT_CONNECT",
+ "SENT_RESOLVE",
+ "SUCCEEDED",
+ "FAILED",
+ "CLOSED",
+ "NEW_CONNECT",
+ "NEW_RESOLVE",
+ "DETACHED"])
+
+# Status codes for "ORCONNSTATUS" events
+OR_CONN_STATUS = _Enum(0x00,
+ ["LAUNCHED","CONNECTED","FAILED","CLOSED"])
+
+# Signal codes for "SIGNAL" events.
+SIGNAL = _Enum2(HUP=0x01,INT=0x02,USR1=0x0A,USR2=0x0C,TERM=0x0F)
+
+# Error codes for "ERROR" events.
+ERR_CODES = {
+ 0x0000 : "Unspecified error",
+ 0x0001 : "Internal error",
+ 0x0002 : "Unrecognized message type",
+ 0x0003 : "Syntax error",
+ 0x0004 : "Unrecognized configuration key",
+ 0x0005 : "Invalid configuration value",
+ 0x0006 : "Unrecognized byte code",
+ 0x0007 : "Unauthorized",
+ 0x0008 : "Failed authentication attempt",
+ 0x0009 : "Resource exhausted",
+ 0x000A : "No such stream",
+ 0x000B : "No such circuit",
+ 0x000C : "No such OR"
+}
+
+def _unpack_singleton_msg(msg):
+ """Helper: unpack a single packet. Return (None, minLength, body-so-far)
+ on incomplete packet or (type,body,rest) on somplete packet
+ """
+ if len(msg) < 4:
+ return None, 4, msg
+ length,type = struct.unpack("!HH",msg)
+ if len(msg) >= 4+length:
+ return type,msg[4:4+length],msg[4+length:]
+ else:
+ return None,4+length,msg
+
+def _minLengthToPack(bytes):
+ """Return the minimum number of bytes needed to pack the message 'smg'"""
+ whole,left = divmod(bytes,65535)
+ if left:
+ return whole*(65535+4)+4+left
+ else:
+ return whole*(65535+4)
+
+def _unpack_msg(msg):
+ "returns as for _unpack_singleton_msg"
+ tp,body,rest = _unpack_singleton_msg(msg)
+ if tp != MSG_TYPE.FRAGMENTHEADER:
+ return tp, body, rest
+
+ if len(body) < 6:
+ raise ProtocolError("FRAGMENTHEADER message too short")
+
+ realType,realLength = struct.unpack("!HL", body[:6])
+
+ # Okay; could the message _possibly_ be here?
+ minLength = _minLengthToPack(realLength+6)
+ if len(msg) < minLength:
+ return None, minLength, msg
+
+ # Okay; optimistically try to build up the msg.
+ soFar = [ body[6:] ]
+ lenSoFarLen = len(body)-6
+ while len(rest)>=4 and lenSoFar < realLength:
+ ln, tp = struct.unpack("!HH", rest[:4])
+ if tp != MSG_TYPE.FRAGMENT:
+ raise ProtocolError("Missing FRAGMENT message")
+ soFar.append(rest[4:4+ln])
+ lenSoFar += ln
+ if 4+ln > len(rest):
+ rest = ""
+ leftInPacket = 4+ln-len(rest)
+ else:
+ rest = rest[4+ln:]
+ leftInPacket=0
+
+ if lenSoFar == realLength:
+ return realType, "".join(soFar), rest
+ elif lenSoFar > realLength:
+ raise ProtocolError("Bad fragmentation: message longer than declared")
+ else:
+ inOtherPackets = realLength-lenSoFar-leftInPacket
+ minLength = _minLengthToPack(inOtherPackets)
+ return None, len(msg)+leftInPacket+inOtherPackets, msg
+
+def _receive_singleton_msg(s):
+ """Read a single packet from the socket s.
+ """
+ body = ""
+ header = s.recv(4)
+ if not header:
+ raise TorCtl.TorCtlClosed()
+ length,type = struct.unpack("!HH",header)
+ if length:
+ while length > len(body):
+ more = s.recv(length-len(body))
+ if not more:
+ raise TorCtl.TorCtlClosed()
+ body += more
+ return length,type,body
+
+def _receive_message(s):
+ """Read a single message (possibly multi-packet) from the socket s."""
+ length, tp, body = _receive_singleton_msg(s)
+ if tp != MSG_TYPE.FRAGMENTHEADER:
+ return length, tp, body
+ if length < 6:
+ raise ProtocolError("FRAGMENTHEADER message too short")
+ realType,realLength = struct.unpack("!HL", body[:6])
+ data = [ body[6:] ]
+ soFar = len(data[0])
+ while 1:
+ length, tp, body = _receive_singleton_msg(s)
+ if tp != MSG_TYPE.FRAGMENT:
+ raise ProtocolError("Missing FRAGMENT message")
+ soFar += length
+ data.append(body)
+ if soFar == realLength:
+ return realLength, realType, "".join(data)
+ elif soFar > realLength:
+ raise ProtocolError("FRAGMENT message too long!")
+
+def pack_message(type, body=""):
+ """Given a message type and optional message body, generate a set of
+ packets to send.
+ """
+ length = len(body)
+ if length < 65536:
+ reqheader = struct.pack("!HH", length, type)
+ return "%s%s"%(reqheader,body)
+
+ fragheader = struct.pack("!HHHL",
+ 65535, MSG_TYPE.FRAGMENTHEADER, type, length)
+ msgs = [ fragheader, body[:65535-6] ]
+ body = body[65535-6:]
+ while body:
+ if len(body) > 65535:
+ fl = 65535
+ else:
+ fl = len(body)
+ fragheader = struct.pack("!HH", MSG_TYPE.FRAGMENT, fl)
+ msgs.append(fragheader)
+ msgs.append(body[:fl])
+ body = body[fl:]
+
+ return "".join(msgs)
+
+def _parseKV(body,sep=" ",term="\n"):
+ """Helper: parse a key/value list of the form [key sep value term]* .
+ Return a list of (k,v)."""
+ res = []
+ for line in body.split(term):
+ if not line: continue
+ k, v = line.split(sep,1)
+ res.append((k,v))
+ return res
+
+def _unterminate(s):
+ """Strip trailing NUL characters from s."""
+ if s[-1] == '\0':
+ return s[:-1]
+ else:
+ return s
+
+class Connection(TorCtl._ConnectionBase):
+ """A Connection represents a connection to the Tor process."""
+ def __init__(self, sock):
+ """Create a Connection to communicate with the Tor process over the
+ socket 'sock'.
+ """
+ TorCtl._ConnectionBase.__init__(self)
+ self._s = sock
+
+ def set_event_handler(self, handler):
+ """Cause future events from the Tor process to be sent to 'handler'.
+ """
+ self._handler = handler
+ self._handleFn = handler.handle0
+
+ def _doSend(self, (type, body)):
+ """Helper: Deliver a command of type 'type' and body 'body' to Tor.
+ """
+ self._s.sendall(pack_message(type, body))
+
+ def _read_reply(self):
+ length, tp, body = _receive_message(self._s)
+ return (tp == MSG_TYPE.EVENT), (tp, body)
+
+ def _sendAndRecv(self, tp, msg="", expectedTypes=(MSG_TYPE.DONE,)):
+ """Helper: Send a command of type 'tp' and body 'msg' to Tor,
+ and wait for a command in response. If the response type is
+ in expectedTypes, return a (tp,body) tuple. If it is an error,
+ raise ErrorReply. Otherwise, raise ProtocolError.
+ """
+
+ tp, msg = self.sendImpl(self._doSend, (tp, msg))
+ if tp in expectedTypes:
+ return tp, msg
+ elif tp == MSG_TYPE.ERROR:
+ if len(msg)<2:
+ raise ProtocolError("(Truncated error message)")
+ errCode, = struct.unpack("!H", msg[:2])
+ raise ErrorReply((errCode,
+ ERR_CODES.get(errCode,"[unrecognized]"),
+ msg[2:]))
+ else:
+ raise ProtocolError("Unexpectd message type 0x%04x"%tp)
+
+ def authenticate(self, secret=""):
+ """Send an authenticating secret to Tor. You'll need to call
+ this method before other commands. You need to use a
+ password if Tor expects one.
+ """
+ self._sendAndRecv(MSG_TYPE.AUTH,secret)
+
+ def get_option(self,name):
+ """Get the value of the configuration option named 'name'. To
+ retrieve multiple values, pass a list for 'name' instead of
+ a string. Returns a list of (key,value) pairs.
+ """
+ if not isinstance(name, str):
+ name = "".join(["%s\n"%s for s in name])
+ tp,body = self._sendAndRecv(MSG_TYPE.GETCONF,name,[MSG_TYPE.CONFVALUE])
+ return _parseKV(body)
+
+ def set_option(self,key,value):
+ """Set the value of the configuration option 'key' to the
+ value 'value'.
+ """
+ self.set_options([key, value])
+
+ def set_options(self,kvlist):
+ """Given a list of (key,value) pairs, set them as configuration
+ options.
+ """
+ msg = "".join(["%s %s\n" for k,v in kvlist])
+ self._sendAndRecv(MSG_TYPE.SETCONF,msg)
+
+ def reset_options(self, keylist):
+ msg = "".join(["%s\n" for k in keylist])
+ self._sendAndRecv(MSG_TYPE.SETCONF,msg)
+
+ def get_info(self,name):
+ """Return the value of the internal information field named
+ 'name'. To retrieve multiple values, pass a list for
+ 'name' instead of a string. Returns a dictionary of
+ key->value mappings.
+ """
+ if not isinstance(name, str):
+ name = "".join(["%s\n"%s for s in name])
+ tp, body = self._sendAndRecv(MSG_TYPE.GETINFO,name,[MSG_TYPE.INFOVALUE])
+ kvs = body.split("\0")
+ d = {}
+ for i in xrange(0,len(kvs)-1,2):
+ d[kvs[i]] = kvs[i+1]
+ return d
+
+ def set_events(self,events):
+ """Change the list of events that the event handler is interested
+ in to those in 'events', which is a list of EVENT_TYPE members
+ or their corresponding strings.
+ """
+ evs = []
+ for ev in events:
+ if isinstance(ev, types.StringType):
+ evs.append(getattr(EVENT_TYPE, ev.upper()))
+ else:
+ evs.append(ev)
+ self._sendAndRecv(MSG_TYPE.SETEVENTS,
+ "".join([struct.pack("!H", event) for event in events]))
+
+ def save_conf(self):
+ """Flush all configuration changes to disk.
+ """
+ self._sendAndRecv(MSG_TYPE.SAVECONF)
+
+ def send_signal(self, sig):
+ """Send the signal 'sig' to the Tor process; 'sig' must be a member of
+ SIGNAL or a corresponding string.
+ """
+ try:
+ sig = sig.upper()
+ except AttributeError:
+ pass
+ sig = { "HUP" : 0x01, "RELOAD" : 0x01,
+ "INT" : 0x02, "SHUTDOWN" : 0x02,
+ "DUMP" : 0x0A, "USR1" : 0x0A,
+ "USR2" : 0x0C, "DEBUG" : 0x0C,
+ "TERM" : 0x0F, "HALT" : 0x0F
+ }.get(sig,sig)
+ self._sendAndRecv(MSG_TYPE.SIGNAL,struct.pack("B",sig))
+
+ def map_address(self, kvList):
+ """Given a list of (old-address,new-address), have Tor redirect
+ streams from old-address to new-address. Old-address can be in a
+ special "dont-care" form of "0.0.0.0" or ".".
+ """
+ msg = [ "%s %s\n"%(k,v) for k,v in kvList ]
+ tp, body = self._sendAndRecv(MSG_TYPE.MAPADDRESS,"".join(msg))
+ return _parseKV(body)
+
+ def extend_circuit(self, circid, hops):
+ """Tell Tor to extend the circuit identified by 'circid' through the
+ servers named in the list "hops".
+ """
+ msg = struct.pack("!L",long(circid)) + ",".join(hops) + "\0"
+ tp, body = self._sendAndRecv(MSG_TYPE.EXTENDCIRCUIT,msg)
+ if len(body) != 4:
+ raise ProtocolError("Extendcircuit reply too short or long")
+ return struct.unpack("!L",body)[0]
+
+ def redirect_stream(self, streamid, newtarget):
+ """Tell Tor to change the target address of the stream identified by
+ 'streamid' from its old value to 'newtarget'."""
+ msg = struct.pack("!L",long(streamid)) + newtarget + "\0"
+ self._sendAndRecv(MSG_TYPE.REDIRECTSTREAM,msg)
+
+ def attach_stream(self, streamid, circid):
+ """Tell Tor To attach stream 'streamid' to circuit 'circid'."""
+ msg = struct.pack("!LL",long(streamid), long(circid))
+ self._sendAndRecv(MSG_TYPE.ATTACHSTREAM,msg)
+
+ def close_stream(self, streamid, reason=0, flags=()):
+ """Close the stream 'streamid'. """
+ msg = struct.pack("!LBB",long(streamid),reason,flags)
+ self._sendAndRecv(MSG_TYPE.CLOSESTREAM,msg)
+
+ def close_circuit(self, circid, flags=()):
+ """Close the circuit 'circid'."""
+ if "IFUNUSED" in flags:
+ flags=1
+ else:
+ flags=0
+ msg = struct.pack("!LB",long(circid),flags)
+ self._sendAndRecv(MSG_TYPE.CLOSECIRCUIT,msg)
+
+ def post_descriptor(self, descriptor):
+ """Tell Tor about a new descriptor in 'descriptor'."""
+ self._sendAndRecv(MSG_TYPE.POSTDESCRIPTOR,descriptor)
Added: blossom/trunk/TorCtl1.py
===================================================================
--- blossom/trunk/TorCtl1.py (rev 0)
+++ blossom/trunk/TorCtl1.py 2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,338 @@
+#!/usr/bin/python
+# TorCtl.py -- Python module to interface with Tor Control interface.
+# Copyright 2005 Nick Mathewson -- See LICENSE for licensing information.
+#$Id: TorCtl1.py,v 1.26 2006/01/10 04:05:31 goodell Exp $
+
+import binascii
+import os
+import re
+import socket
+import sys
+import types
+import TorCtl
+
+def _quote(s):
+ return re.sub(r'([\r\n\\\"])', r'\\\1', s)
+
+def _escape_dots(s, translate_nl=1):
+ if translate_nl:
+ lines = re.split(r"\r?\n", s)
+ else:
+ lines = s.split("\r\n")
+ if lines and not lines[-1]:
+ del lines[-1]
+ for i in xrange(len(lines)):
+ if lines[i].startswith("."):
+ lines[i] = "."+lines[i]
+ lines.append(".\r\n")
+ return "\r\n".join(lines)
+
+def _unescape_dots(s, translate_nl=1):
+ lines = s.split("\r\n")
+
+ for i in xrange(len(lines)):
+ if lines[i].startswith("."):
+ lines[i] = lines[i][1:]
+
+ if lines and lines[-1]:
+ lines.append("")
+
+ if translate_nl:
+ return "\n".join(lines)
+ else:
+ return "\r\n".join(lines)
+
+class _BufSock:
+ def __init__(self, s):
+ self._s = s
+ self._buf = []
+
+ def readline(self):
+ if self._buf:
+ idx = self._buf[0].find('\n')
+ if idx >= 0:
+ result = self._buf[0][:idx+1]
+ self._buf[0] = self._buf[0][idx+1:]
+ return result
+
+ while 1:
+ s = self._s.recv(128)
+ if not s:
+ raise TorCtl.TorCtlClosed()
+ idx = s.find('\n')
+ if idx >= 0:
+ self._buf.append(s[:idx+1])
+ result = "".join(self._buf)
+ rest = s[idx+1:]
+ if rest:
+ self._buf = [ rest ]
+ else:
+ del self._buf[:]
+ return result
+ else:
+ self._buf.append(s)
+
+ def write(self, s):
+ self._s.send(s)
+
+ def close(self):
+ self._s.close()
+
+def _read_reply(f,debugFile=None):
+ lines = []
+ while 1:
+ line = f.readline().strip()
+ if debugFile:
+ debugFile.write(" %s\n" % line)
+ if len(line)<4:
+ raise TorCtl.ProtocolError("Badly formatted reply line: Too short")
+ code = line[:3]
+ tp = line[3]
+ s = line[4:]
+ if tp == "-":
+ lines.append((code, s, None))
+ elif tp == " ":
+ lines.append((code, s, None))
+ return lines
+ elif tp != "+":
+ raise TorCtl.ProtocolError("Badly formatted reply line: unknown type %r"%tp)
+ else:
+ more = []
+ while 1:
+ line = f.readline()
+ if debugFile and tp != "+":
+ debugFile.write(" %s" % line)
+ if line in (".\r\n", ".\n"):
+ break
+ more.append(line)
+ lines.append((code, s, _unescape_dots("".join(more))))
+
+class Connection(TorCtl._ConnectionBase):
+ """A Connection represents a connection to the Tor process."""
+ def __init__(self, sock):
+ """Create a Connection to communicate with the Tor process over the
+ socket 'sock'.
+ """
+ TorCtl._ConnectionBase.__init__(self)
+ self._s = _BufSock(sock)
+ self._debugFile = None
+
+ def debug(self, f):
+ """DOCDOC"""
+ self._debugFile = f
+
+ def set_event_handler(self, handler):
+ """Cause future events from the Tor process to be sent to 'handler'.
+ """
+ self._handler = handler
+ self._handleFn = handler.handle1
+
+ def _read_reply(self):
+ lines = _read_reply(self._s, self._debugFile)
+ isEvent = (lines and lines[0][0][0] == '6')
+ return isEvent, lines
+
+ def _doSend(self, msg):
+ if self._debugFile:
+ amsg = msg
+ lines = amsg.split("\n")
+ if len(lines) > 2:
+ amsg = "\n".join(lines[:2]) + "\n"
+ self._debugFile.write(">>> %s" % amsg)
+ self._s.write(msg)
+
+ def _sendAndRecv(self, msg="", expectedTypes=("250", "251")):
+ """Helper: Send a command 'msg' to Tor, and wait for a command
+ in response. If the response type is in expectedTypes,
+ return a list of (tp,body,extra) tuples. If it is an
+ error, raise ErrorReply. Otherwise, raise TorCtl.ProtocolError.
+ """
+ if type(msg) == types.ListType:
+ msg = "".join(msg)
+ assert msg.endswith("\r\n")
+
+ lines = self._sendImpl(self._doSend, msg)
+ # print lines
+ for tp, msg, _ in lines:
+ if tp[0] in '45':
+ raise TorCtl.ErrorReply("%s %s"%(tp, msg))
+ if tp not in expectedTypes:
+ raise TorCtl.ProtocolError("Unexpectd message type %r"%tp)
+
+ return lines
+
+ def authenticate(self, secret=""):
+ """Send an authenticating secret to Tor. You'll need to call this
+ method before Tor can start.
+ """
+ hexstr = binascii.b2a_hex(secret)
+ self._sendAndRecv("AUTHENTICATE %s\r\n"%hexstr)
+
+ def get_option(self, name):
+ """Get the value of the configuration option named 'name'. To
+ retrieve multiple values, pass a list for 'name' instead of
+ a string. Returns a list of (key,value) pairs.
+ Refer to section 3.3 of control-spec.txt for a list of valid names.
+ """
+ if not isinstance(name, str):
+ name = " ".join(name)
+ lines = self._sendAndRecv("GETCONF %s\r\n" % name)
+
+ r = []
+ for _,line,_ in lines:
+ try:
+ key, val = line.split("=", 1)
+ r.append((key,val))
+ except ValueError:
+ r.append((line, None))
+
+ return r
+
+ def set_option(self, key, value):
+ """Set the value of the configuration option 'key' to the value 'value'.
+ """
+ self.set_options([(key, value)])
+
+ def set_options(self, kvlist):
+ """Given a list of (key,value) pairs, set them as configuration
+ options.
+ """
+ if not kvlist:
+ return
+ msg = " ".join(["%s=%s"%(k,_quote(v)) for k,v in kvlist])
+ self._sendAndRecv("SETCONF %s\r\n"%msg)
+
+ def reset_options(self, keylist):
+ """Reset the options listed in 'keylist' to their default values.
+
+ Tor started implementing this command in version 0.1.1.7-alpha;
+ previous versions wanted you to set configuration keys to "".
+ That no longer works.
+ """
+ self._sendAndRecv("RESETCONF %s\r\n"%(" ".join(keylist)))
+
+ def get_info(self, name):
+ """Return the value of the internal information field named 'name'.
+ Refer to section 3.9 of control-spec.txt for a list of valid names.
+ DOCDOC
+ """
+ if not isinstance(name, str):
+ name = " ".join(name)
+ lines = self._sendAndRecv("GETINFO %s\r\n"%name)
+ d = {}
+ for _,msg,more in lines:
+ if msg == "OK":
+ break
+ try:
+ k,rest = msg.split("=",1)
+ except ValueError:
+ raise TorCtl.ProtocolError("Bad info line %r",msg)
+ if more:
+ d[k] = more
+ else:
+ d[k] = rest
+ return d
+
+ def set_events(self, events):
+ """Change the list of events that the event handler is interested
+ in to those in 'events', which is a list of event names.
+ Recognized event names are listed in section 3.3 of the control-spec
+ """
+ evs = []
+
+ # Translate options supported by old interface.
+ for e in events:
+ if e == 0x0001 or e == "CIRCSTATUS":
+ e = "CIRC"
+ elif e == 0x0002 or e == "STREAMSTATUS":
+ e = "STREAM"
+ elif e == 0x0003 or e == "ORCONNSTATUS":
+ e = "ORCONN"
+ elif e == 0x0004 or e == "BANDWIDTH":
+ e = "BW"
+ elif e == 0x0005 or e == "OBSOLETE_LOG":
+ coneinue
+ elif e == 0x0006 or e == "NEWDESC":
+ e = "NEWDESC"
+ elif e == 0x0007 or e == "DEBUG_MSG":
+ continue
+ elif e == 0x0008 or e == "INFO_MSG":
+ e = "INFO"
+ elif e == 0x0008 or e == "NOTICE_MSG":
+ e = "NOTICE"
+ elif e == 0x0008 or e == "WARN_MSG":
+ e = "WARN"
+ elif e == 0x0008 or e == "ERR_MSG":
+ e = "ERR"
+ evs.append(e)
+
+ self._sendAndRecv("SETEVENTS %s\r\n" % " ".join(evs))
+
+ def save_conf(self):
+ """Flush all configuration changes to disk.
+ """
+ self._sendAndRecv("SAVECONF\r\n")
+
+ def send_signal(self, sig):
+ """Send the signal 'sig' to the Tor process; The allowed values for
+ 'sig' are listed in section 3.6 of control-spec.
+ """
+ sig = { 0x01 : "HUP",
+ 0x02 : "INT",
+ 0x0A : "USR1",
+ 0x0C : "USR2",
+ 0x0F : "TERM" }.get(sig,sig)
+ self._sendAndRecv("SIGNAL %s\r\n"%sig)
+
+ def map_address(self, kvList):
+ if not kvList:
+ return
+ m = " ".join([ "%s=%s" for k,v in kvList])
+ lines = self._sendAndRecv("MAPADDRESS %s\r\n"%m)
+ r = []
+ for _,line,_ in lines:
+ try:
+ key, val = line.split("=", 1)
+ except ValueError:
+ raise TorCtl.ProtocolError("Bad address line %r",v)
+ r.append((key,val))
+ return r
+
+ def extend_circuit(self, circid, hops):
+ """Tell Tor to extend the circuit identified by 'circid' through the
+ servers named in the list 'hops'.
+ """
+ if circid is None:
+ circid = "0"
+ lines = self._sendAndRecv("EXTENDCIRCUIT %s %s\r\n"
+ %(circid, ",".join(hops)))
+ tp,msg,_ = lines[0]
+ m = re.match(r'EXTENDED (\S*)', msg)
+ if not m:
+ raise TorCtl.ProtocolError("Bad extended line %r",msg)
+ return m.group(1)
+
+ def redirect_stream(self, streamid, newaddr, newport=""):
+ """DOCDOC"""
+ if newport:
+ self._sendAndRecv("REDIRECTSTREAM %s %s %s\r\n"%(streamid, newaddr, newport))
+ else:
+ self._sendAndRecv("REDIRECTSTREAM %s %s\r\n"%(streamid, newaddr))
+
+ def attach_stream(self, streamid, circid):
+ """DOCDOC"""
+ self._sendAndRecv("ATTACHSTREAM %s %s\r\n"%(streamid, circid))
+
+ def close_stream(self, streamid, reason=0, flags=()):
+ """DOCDOC"""
+ self._sendAndRecv("CLOSESTREAM %s %s %s\r\n"
+ %(streamid, reason, "".join(flags)))
+
+ def close_circuit(self, circid, reason=0, flags=()):
+ """DOCDOC"""
+ self._sendAndRecv("CLOSECIRCUIT %s %s %s\r\n"
+ %(circid, reason, "".join(flags)))
+
+ def post_descriptor(self, desc):
+ self._sendAndRecv("+POSTDESCRIPTOR\r\n%s"%_escape_dots(desc))
+
Added: blossom/trunk/blossom.pl
===================================================================
--- blossom/trunk/blossom.pl (rev 0)
+++ blossom/trunk/blossom.pl 2007-07-16 07:09:49 UTC (rev 10838)
@@ -0,0 +1,442 @@
+#!/usr/bin/perl -w
+# $Id: blossom.pl,v 1.19 2006-03-20 23:19:33 goodell Exp $
+$license = <<EOF
+Copyright (c) 2005 Geoffrey Goodell.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of version 2 of the GNU General Public License as published by the
+Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+Place - Suite 330, Boston, MA 02111-1307, USA.
+
+EOF
+;
+
+use strict;
+use Socket;
+
+# global configuration parameters
+
+my $CACHE = "/var/cache/www-data";
+my $F_CCODES = "/afs/eecs.harvard.edu/user/goodell/misc/country-codes.txt";
+my $URL_FLAGS = "http://afs.eecs.harvard.edu/~goodell/flags";
+my $URL_ICONS = "http://afs.eecs.harvard.edu/~goodell/icons";
+my $URL_EXIT = "http://serifos.eecs.harvard.edu/cgi-bin/exit.pl";
+my $URL_PROXY = "http://serifos.eecs.harvard.edu/proxy/";
+my $URL_SELF = "http://serifos.eecs.harvard.edu/cgi-bin/blossom.pl";
+my $URL_SOURCE = "http://afs.eecs.harvard.edu/~goodell/blossom/src/blossom.pl";
+my $URL_HOME = "http://afs.eecs.harvard.edu/~goodell/blossom/";
+my $WHOIS_SCRIPT = "/cgi-bin/whois.pl";
+my $STATUS = "?ports=80&addr=1&textonly=1";
+my $BLOSSOM = "$STATUS&blossom=lefkada:9031";
+my $BLOSSOM_TAG = "U0";
+my $BLOSSOM_TEXT = "Blossom";
+my $TITLE = "Blossom User Interface";
+my $WGET = "/usr/bin/wget -O -";
+my $ICON_V1 = "v1.gif";
+my $ICON_V2 = "v2.gif";
+my $ICON_V3 = "v3.gif";
+my $F_SIZE = "width=18 height=12";
+my $V1_MINBW = 10;
+my $V2_MINBW = 60;
+my $V3_MINBW = 400;
+
+my %ccode = ();
+my %nodes = ();
+my %uri_fields = ();
+
+my $cachefile = "blossom.html";
+my $response = "";
+my $uri = "";
+my $method = undef;
+
+use vars qw($license);
+
+sub addrouters($) {
+ my $href = shift;
+ my @sorted = undef;
+ my $response = "";
+ my %routers = %$href;
+
+ @sorted = sort keys %routers;
+
+ foreach my $router (@sorted) {
+ $response .= "<tr>\n " . $routers{$router} . "</tr>\n";
+ }
+ return $response;
+}
+
+sub parsewhois($$$) {
+ my ($tag, $default, $arrayref) = (shift, shift, shift);
+ my $t;
+ my @lines = @$arrayref;
+ my @matches = grep /^$tag/i, @lines;
+
+ chomp $matches[$#matches] if $matches[$#matches];
+ ($t = $matches[$#matches] || $default) =~ s/\S+\s+//;
+ return $t;
+}
+
+sub modify_url($$) {
+ my ($modurl, $suffix) = (shift, shift);
+
+ unless($modurl eq "") {
+ $modurl =~ s/%3A/:/g;
+ $modurl =~ s/%2F/\//g;
+
+ $modurl = "http://$modurl" if $modurl !~ /^http:\/\//;
+ $modurl = "$modurl/" if $modurl !~ /^http:\/\/.*\//;
+
+ if($modurl =~ /^(http:\/\/[A-Za-z0-9-.]+)([\/:].*)$/) {
+ $modurl = "$1.$suffix$2";
+ }
+
+ $modurl = "$URL_PROXY$modurl" if $uri_fields{"proxy"};
+ }
+ return $modurl;
+}
+
+sub report_router($$$$$$) {
+ my ($router, $address, $bandwidth, $netname, $modurl, $b)
+ = (shift, shift, shift, shift, shift, shift);
+
+ my $ab = "";
+ my $bb = "";
+ my $cb = "";
+ my $icon = "";
+ my $r = "";
+
+ if($b) {
+ $ab = " class=\"unverified\"";
+ $bb = " class=\"unverified\"";
+ $cb = "*";
+ } else {
+ $ab = " class=\"standard\"";
+ $bb = "";
+ $cb = "";
+ }
+
+ my $modlink = "<a$ab href=\"$modurl\">$cb$router</a>";
+
+ # security feature
+ $address = "" if $address !~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/;
+
+ if($bandwidth >= $V3_MINBW*1000) {
+ $icon = "<img $F_SIZE src=\"$URL_ICONS/$ICON_V3\" alt=\"v3\">";
+ } elsif($bandwidth >= $V2_MINBW*1000) {
+ $icon = "<img $F_SIZE src=\"$URL_ICONS/$ICON_V2\" alt=\"v2\">";
+ } else {
+ $icon = "<img $F_SIZE src=\"$URL_ICONS/$ICON_V1\" alt=\"v1\">";
+ }
+ $icon = "<acronym title=\"$bandwidth B/s\">$icon</acronym>";
+
+ $bandwidth = sprintf "%4s kB/s", int($bandwidth/1000);
+ $bandwidth =~ s/ / /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):
+