|
@@ -0,0 +1,309 @@
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+import socket
|
|
|
+import struct
|
|
|
+import sys
|
|
|
+
|
|
|
+class _Enum:
|
|
|
+ def __init__(self, start, names):
|
|
|
+ self.nameOf = {}
|
|
|
+ idx = start
|
|
|
+ for name in names:
|
|
|
+ setattr(self,name,idx)
|
|
|
+ self.nameOf[idx] = name
|
|
|
+ idx += 1
|
|
|
+
|
|
|
+
|
|
|
+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",
|
|
|
+ ])
|
|
|
+
|
|
|
+assert MSG_TYPE.SAVECONF = 0x0008
|
|
|
+assert MSG_TYPE.CLOSECIRCUIT = 0x0014
|
|
|
+
|
|
|
+EVENT_TYPE = _ENUM(0x0001,
|
|
|
+ ["CIRCSTATUS",
|
|
|
+ "STREAMSTATUS",
|
|
|
+ "ORCONNSTATUS",
|
|
|
+ "BANDWIDTH",
|
|
|
+ "WARN",
|
|
|
+ "NEWDESC"])
|
|
|
+
|
|
|
+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"
|
|
|
+}
|
|
|
+
|
|
|
+class TorCtlError(Exception):
|
|
|
+ pass
|
|
|
+
|
|
|
+class ProtocolError(TorCtlError):
|
|
|
+ pass
|
|
|
+
|
|
|
+class ErrorReply(TorCtlError):
|
|
|
+ pass
|
|
|
+
|
|
|
+def parseHostAndPort(h):
|
|
|
+ host, port = "localhost", 9051
|
|
|
+ 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 _unpack_msg(msg):
|
|
|
+ "return None, minLength, body or type,body,rest"
|
|
|
+ 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 unpack_msg(msg):
|
|
|
+ "returns as for _unpack_msg"
|
|
|
+ tp,body,rest = _unpack_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])
|
|
|
+
|
|
|
+
|
|
|
+ minPackets,minSlop = divmod(realLength+6,65535)
|
|
|
+ minLength = (minPackets*(65535+4))+4+minSlop
|
|
|
+ if len(msg) < minLength:
|
|
|
+ return None, minLength, msg
|
|
|
+
|
|
|
+
|
|
|
+ soFar = [ body[6:] ]
|
|
|
+ lenSoFarLen = len(body)-6
|
|
|
+ while rest 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
|
|
|
+ rest = rest[4+ln:]
|
|
|
+
|
|
|
+ if lenSoFar == realLength:
|
|
|
+ return realType, "".join(soFar), rest
|
|
|
+ elif lenSoFar > realLength:
|
|
|
+ raise ProtocolError("Bad fragmentation: message longer than declared")
|
|
|
+ else:
|
|
|
+ return None, len(msg)+(realLength-lenSoFar), msg
|
|
|
+
|
|
|
+def receive_message(s):
|
|
|
+ length, tp, body = _receive_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_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!")
|
|
|
+
|
|
|
+_event_handler = None
|
|
|
+def receive_reply(s, expected=None):
|
|
|
+ while 1:
|
|
|
+ _, tp, body = receive_message(s)
|
|
|
+ if tp == MSG_TYPE.EVENT:
|
|
|
+ if _event_handler is not None:
|
|
|
+ _event_handler(tp, body)
|
|
|
+ elif tp == MSG_TYPE.ERROR:
|
|
|
+ if len(body)<2:
|
|
|
+ raise ProtocolError("(Truncated error message)")
|
|
|
+ errCode, = struct.unpack("!H", body[:2])
|
|
|
+ raise ErrorReply((errCode,
|
|
|
+ ERR_CODES.get(errCode,"[unrecognized]"),
|
|
|
+ body[2:]))
|
|
|
+ elif (expected is not None) and (tp not in expected):
|
|
|
+ raise ProtocolError("Unexpected message type 0x%04x"%tp)
|
|
|
+ else:
|
|
|
+ return tp, body
|
|
|
+
|
|
|
+def pack_message(type, body=""):
|
|
|
+ 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 send_message(s, type, body=""):
|
|
|
+ s.sendall(pack_message(type, body))
|
|
|
+
|
|
|
+def authenticate(s):
|
|
|
+ send_message(s,MSG_TYPE.AUTH)
|
|
|
+ type,body = receive_reply(s)
|
|
|
+ return
|
|
|
+
|
|
|
+def _parseKV(body,sep=" ",term="\n"):
|
|
|
+ res = []
|
|
|
+ for line in body.split(term):
|
|
|
+ if not line: continue
|
|
|
+ print repr(line)
|
|
|
+ k, v = line.split(sep,1)
|
|
|
+ res.append((k,v))
|
|
|
+ return res
|
|
|
+
|
|
|
+def get_option(s,name):
|
|
|
+ send_message(s,MSG_TYPE.GETCONF,name)
|
|
|
+ tp,body = receive_reply(s,[MSG_TYPE.CONFVALUE])
|
|
|
+ return _parseKV(body)
|
|
|
+
|
|
|
+def set_option(s,msg):
|
|
|
+ send_message(s,MSG_TYPE.SETCONF,msg)
|
|
|
+ tp,body = receive_reply(s,[MSG_TYPE.DONE])
|
|
|
+
|
|
|
+def get_info(s,name):
|
|
|
+ send_message(s,MSG_TYPE.GETINFO,name)
|
|
|
+ tp,body = receive_reply(s,[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(s,events):
|
|
|
+ send_message(s,MSG_TYPE.SETEVENTS,
|
|
|
+ "".join([struct.pack("!H", event) for event in events]))
|
|
|
+ type,body = receive_reply(s,[MSG_TYPE.DONE])
|
|
|
+ return
|
|
|
+
|
|
|
+def save_conf(s):
|
|
|
+ send_message(s,MSG_TYPE.SAVECONF)
|
|
|
+ receive_reply(s,[MSG_TYPE.DONE])
|
|
|
+
|
|
|
+def send_signal(s, sig):
|
|
|
+ send_message(s,MSG_TYPE.SIGNAL,struct.pack("B",sig))
|
|
|
+ receive_reply(s,[MSG_TYPE.DONE])
|
|
|
+
|
|
|
+def map_address(s, kv):
|
|
|
+ msg = [ "%s %s\n"%(k,v) for k,v in kv ]
|
|
|
+ send_message(s,MSG_TYPE.MAPADDRESS,"".join(msg))
|
|
|
+ tp, body = receive_reply(s,[MSG_TYPE.DONE])
|
|
|
+ return _parseKV(body)
|
|
|
+
|
|
|
+def extend_circuit(s, circid, hops):
|
|
|
+ msg = struct.pack("!L",circid) + ",".join(hops) + "\0"
|
|
|
+ send_message(s,MSG_TYPE.EXTENDCIRCUIT,msg)
|
|
|
+ tp, body = receive_reply(s,[MSG_TYPE.DONE])
|
|
|
+ return body
|
|
|
+
|
|
|
+def listen_for_events(s):
|
|
|
+ while(1):
|
|
|
+ _,type,body = receive_message(s)
|
|
|
+ print "event",type
|
|
|
+ return
|
|
|
+
|
|
|
+def do_main_loop(host,port):
|
|
|
+ print "host is %s:%d"%(host,port)
|
|
|
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
+ s.connect((host,port))
|
|
|
+ authenticate(s)
|
|
|
+ print "nick",`get_option(s,"nickname")`
|
|
|
+ print get_option(s,"DirFetchPeriod\n")
|
|
|
+ print `get_info(s,"version")`
|
|
|
+
|
|
|
+ print `get_info(s,"network-status")`
|
|
|
+ print `get_info(s,"addr-mappings/all")`
|
|
|
+ print `get_info(s,"addr-mappings/config")`
|
|
|
+ print `get_info(s,"addr-mappings/cache")`
|
|
|
+ print `get_info(s,"addr-mappings/control")`
|
|
|
+ print `map_address(s, [("0.0.0.0", "Foobar.com"),
|
|
|
+ ("1.2.3.4", "foobaz.com"),
|
|
|
+ ("frebnitz.com", "5.6.7.8"),
|
|
|
+ (".", "abacinator.onion")])`
|
|
|
+ print `extend_circuit(s,0,["moria1"])`
|
|
|
+ send_signal(s,1)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ set_events(s,[EVENT_TYPE.WARN,EVENT_TYPE.STREAMSTATUS])
|
|
|
+
|
|
|
+ listen_for_events(s)
|
|
|
+
|
|
|
+ return
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ if len(sys.argv) != 2:
|
|
|
+ print "Syntax: tor-control.py torhost:torport"
|
|
|
+ sys.exit(0)
|
|
|
+ sh,sp = parseHostAndPort(sys.argv[1])
|
|
|
+ do_main_loop(sh,sp)
|
|
|
+
|