[or-cvs] r15746: Document new TorCtl package, update example, remove old file (in torctl/trunk/python: . TorCtl)
mikeperry at seul.org
mikeperry at seul.org
Tue Jul 8 05:07:19 UTC 2008
Author: mikeperry
Date: 2008-07-08 01:07:19 -0400 (Tue, 08 Jul 2008)
New Revision: 15746
Removed:
torctl/trunk/python/TorCtl.py
torctl/trunk/python/TorCtl0.py
torctl/trunk/python/TorCtl1.py
Modified:
torctl/trunk/python/README
torctl/trunk/python/TorCtl/GeoIPSupport.py
torctl/trunk/python/TorExample.py
Log:
Document new TorCtl package, update example, remove old
files.
Modified: torctl/trunk/python/README
===================================================================
--- torctl/trunk/python/README 2008-07-08 03:48:18 UTC (rev 15745)
+++ torctl/trunk/python/README 2008-07-08 05:07:19 UTC (rev 15746)
@@ -1,2 +1,44 @@
-We broke the version detection stuff in Tor 0.1.2.16 / 0.2.0.4-alpha.
-Go use the python libs in the torflow module; these are now obsolete. -RD
+ TorCtl Python Bindings
+
+
+TorCtl is a python Tor controller with extensions to support path
+building and various constraints on node and path selection, as well as
+statistics gathering.
+
+Apps can hook into the TorCtl package at whatever level they wish.
+
+The lowest level of interaction is to use the TorCtl module
+(TorCtl/TorCtl.py). Typically this is done by importing TorCtl.TorCtl
+and creating a TorCtl.Connection and extending from TorCtl.EventHandler.
+This class receives Tor controller events packaged into python classes
+from a TorCtl.Connection.
+
+The next level up is to use the TorCtl.PathSupport module. This is done
+by importing TorCtl.PathSupport and instantiating or extending from
+PathSupport.PathBuilder, which itself extends from TorCtl.EventHandler.
+This class handles circuit construction and stream attachment subject to
+policies defined by PathSupport.NodeRestrictor and
+PathSupport.PathRestrictor implementations.
+
+If you are interested in gathering statistics, you can instead
+instantiate or extend from StatsSupport.StatsHandler, which is
+again an event handler with hooks to record statistics on circuit
+creation, stream bandwidth, and circuit failure information.
+
+All of these modules are pydoced. For more detailed information than
+the above overview, you can do:
+
+# pydoc TorCtl.TorCtl
+# pydoc TorCtl.PathSupport
+# pydoc TorCtl.StatsSupport
+
+There is a minimalistic example of usage of the basic TorCtl.Connection
+and TorCtl.EventHandler in TorExample.py in this directory.
+
+For more extensive examples of the PathSupport and StatsSupport
+interfaces, see the TorFlow project at svn urls:
+
+https://tor-svn.freehaven.net/svn/torflow/trunk
+or
+https://tor-svn.freehaven.net/svn/torflow/branches/gsoc2008
+
Modified: torctl/trunk/python/TorCtl/GeoIPSupport.py
===================================================================
--- torctl/trunk/python/TorCtl/GeoIPSupport.py 2008-07-08 03:48:18 UTC (rev 15745)
+++ torctl/trunk/python/TorCtl/GeoIPSupport.py 2008-07-08 05:07:19 UTC (rev 15746)
@@ -2,14 +2,18 @@
import struct
import socket
-import GeoIP
import TorCtl
from TorUtil import plog
+try:
+ import GeoIP
+ # GeoIP data object: choose database here
+ geoip = GeoIP.new(GeoIP.GEOIP_STANDARD)
+ #geoip = GeoIP.open("./GeoLiteCity.dat", GeoIP.GEOIP_STANDARD)
+except:
+ plog("NOTICE", "No GeoIP library. GeoIPSupport.py will not work correctly")
+ # XXX: How do we bail entirely..
-# GeoIP data object: choose database here
-geoip = GeoIP.new(GeoIP.GEOIP_STANDARD)
-#geoip = GeoIP.open("./GeoLiteCity.dat", GeoIP.GEOIP_STANDARD)
class Continent:
""" Continent class: The group attribute is to partition the continents
Deleted: torctl/trunk/python/TorCtl.py
===================================================================
--- torctl/trunk/python/TorCtl.py 2008-07-08 03:48:18 UTC (rev 15745)
+++ torctl/trunk/python/TorCtl.py 2008-07-08 05:07:19 UTC (rev 15746)
@@ -1,536 +0,0 @@
-#!/usr/bin/python
-# TorCtl.py -- Python module to interface with Tor Control interface.
-# Copyright 2005 Nick Mathewson -- See LICENSE for licensing information.
-#$Id$
-
-"""
-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)
-
Deleted: torctl/trunk/python/TorCtl0.py
===================================================================
--- torctl/trunk/python/TorCtl0.py 2008-07-08 03:48:18 UTC (rev 15745)
+++ torctl/trunk/python/TorCtl0.py 2008-07-08 05:07:19 UTC (rev 15746)
@@ -1,446 +0,0 @@
-#!/usr/bin/python
-# TorCtl.py -- Python module to interface with Tor Control interface.
-# Copyright 2005 Nick Mathewson -- See LICENSE for licensing information.
-#$Id$
-
-"""
-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)
Deleted: torctl/trunk/python/TorCtl1.py
===================================================================
--- torctl/trunk/python/TorCtl1.py 2008-07-08 03:48:18 UTC (rev 15745)
+++ torctl/trunk/python/TorCtl1.py 2008-07-08 05:07:19 UTC (rev 15746)
@@ -1,338 +0,0 @@
-#!/usr/bin/python
-# TorCtl.py -- Python module to interface with Tor Control interface.
-# Copyright 2005 Nick Mathewson -- See LICENSE for licensing information.
-#$Id$
-
-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))
-
Modified: torctl/trunk/python/TorExample.py
===================================================================
--- torctl/trunk/python/TorExample.py 2008-07-08 03:48:18 UTC (rev 15745)
+++ torctl/trunk/python/TorExample.py 2008-07-08 05:07:19 UTC (rev 15746)
@@ -33,7 +33,7 @@
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.
"""
- hostport = "localhost:9100"
+ hostport = "localhost:9051"
verbose = 0
while sys.argv[1][0] == '-':
if sys.argv[1] == '--host':
@@ -46,18 +46,18 @@
verbose = 1
del sys.argv[1]
- host,port = parseHostAndPort(hostport)
+ host,port = TorCtl.parseHostAndPort(hostport)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((host,port))
except socket.error, e:
print "Connection failed: %s. Is the ControlPort enabled?"%e
sys.exit(1)
- conn = get_connection(s)
+ conn = TorCtl.Connection(s)
if verbose and hasattr(conn, "debug"):
conn.debug(sys.stdout)
th = conn.launch_thread(daemon)
- conn.authenticate("")
+ conn.authenticate("handsoffmytor")
return conn
def run():
@@ -83,7 +83,7 @@
return
try:
fn()
- except ErrorReply, e:
+ except TorCtl.ErrorReply, e:
print "Request failed: %s"%e
def run_set_config():
@@ -133,9 +133,8 @@
in order to stop it
"""
conn = getConnection(daemon=0)
- events = []
print "listening!"
- conn.set_event_handler(DebugEventHandler())
+ conn.set_event_handler(TorCtl.DebugEventHandler())
conn.set_events(sys.argv[1:])
def run_signal():
More information about the tor-commits
mailing list