Loops without an increment step can suck, even on windows.
[tor.git] / contrib / TorControl.py
blob2cc7cca133116ab0387189f48c6d9c9da4ff9ff4
1 #!/usr/bin/python
2 # TorControl.py -- Python module to interface with Tor Control interface.
3 # Copyright 2005 Nick Mathewson -- See LICENSE for licensing information.
4 #$Id$
6 # THIS MODULE IS OBSOLETE!
8 # There is a "TorCtl.py" module in the "control" directory in Tor CVS;
9 # this new module supports the new ('version 1') Tor controller protocol
10 # and has a far nicer and more object-oriented design than this module does.
12 # No further support will be done on this module.
14 import socket
15 import struct
16 import sys
18 #__all__ = [ "MSG_TYPE", "" ]
20 class _Enum:
21 # Helper: define an ordered dense name-to-number 1-1 mapping.
22 def __init__(self, start, names):
23 self.nameOf = {}
24 idx = start
25 for name in names:
26 setattr(self,name,idx)
27 self.nameOf[idx] = name
28 idx += 1
29 class _Enum2:
30 # Helper: define an ordered sparse name-to-number 1-1 mapping.
31 def __init__(self, **args):
32 self.__dict__.update(args)
33 self.nameOf = {}
34 for k,v in args.items():
35 self.nameOf[v] = k
37 # Message types that client or server can send.
38 MSG_TYPE = _Enum(0x0000,
39 ["ERROR",
40 "DONE",
41 "SETCONF",
42 "GETCONF",
43 "CONFVALUE",
44 "SETEVENTS",
45 "EVENT",
46 "AUTH",
47 "SAVECONF",
48 "SIGNAL",
49 "MAPADDRESS",
50 "GETINFO",
51 "INFOVALUE",
52 "EXTENDCIRCUIT",
53 "ATTACHSTREAM",
54 "POSTDESCRIPTOR",
55 "FRAGMENTHEADER",
56 "FRAGMENT",
57 "REDIRECTSTREAM",
58 "CLOSESTREAM",
59 "CLOSECIRCUIT",
62 # Make sure that the enumeration code is working.
63 assert MSG_TYPE.SAVECONF == 0x0008
64 assert MSG_TYPE.CLOSECIRCUIT == 0x0014
66 # Types of "EVENT" message.
67 EVENT_TYPE = _Enum(0x0001,
68 ["CIRCSTATUS",
69 "STREAMSTATUS",
70 "ORCONNSTATUS",
71 "BANDWIDTH",
72 "OBSOLETE_LOG",
73 "NEWDESC",
74 "DEBUG_MSG",
75 "INFO_MSG",
76 "NOTICE_MSG",
77 "WARN_MSG",
78 "ERR_MSG",
81 assert EVENT_TYPE.ERR_MSG == 0x000B
82 assert EVENT_TYPE.OBSOLETE_LOG == 0x0005
84 # Status codes for "CIRCSTATUS" events.
85 CIRC_STATUS = _Enum(0x00,
86 ["LAUNCHED",
87 "BUILT",
88 "EXTENDED",
89 "FAILED",
90 "CLOSED"])
92 # Status codes for "STREAMSTATUS" events
93 STREAM_STATUS = _Enum(0x00,
94 ["SENT_CONNECT",
95 "SENT_RESOLVE",
96 "SUCCEEDED",
97 "FAILED",
98 "CLOSED",
99 "NEW_CONNECT",
100 "NEW_RESOLVE",
101 "DETACHED"])
103 # Status codes for "ORCONNSTATUS" events
104 OR_CONN_STATUS = _Enum(0x00,
105 ["LAUNCHED","CONNECTED","FAILED","CLOSED"])
107 # Signal codes for "SIGNAL" events.
108 SIGNAL = _Enum2(HUP=0x01,INT=0x02,USR1=0x0A,USR2=0x0C,TERM=0x0F)
110 # Error codes for "ERROR" events.
111 ERR_CODES = {
112 0x0000 : "Unspecified error",
113 0x0001 : "Internal error",
114 0x0002 : "Unrecognized message type",
115 0x0003 : "Syntax error",
116 0x0004 : "Unrecognized configuration key",
117 0x0005 : "Invalid configuration value",
118 0x0006 : "Unrecognized byte code",
119 0x0007 : "Unauthorized",
120 0x0008 : "Failed authentication attempt",
121 0x0009 : "Resource exhausted",
122 0x000A : "No such stream",
123 0x000B : "No such circuit",
124 0x000C : "No such OR"
127 class TorCtlError(Exception):
128 "Generic error raised by TorControl code."
129 pass
131 class ProtocolError(TorCtlError):
132 "Raised on violations in Tor controller protocol"
133 pass
135 class ErrorReply(TorCtlError):
137 pass
139 def parseHostAndPort(h):
140 host, port = "localhost", 9051
141 if ":" in h:
142 i = h.index(":")
143 host = h[:i]
144 try:
145 port = int(h[i+1:])
146 except ValueError:
147 print "Bad hostname %r"%h
148 sys.exit(1)
149 elif h:
150 try:
151 port = int(h)
152 except ValueError:
153 host = h
155 return host, port
157 def _unpack_msg(msg):
158 "return None, minLength, body or type,body,rest"
159 if len(msg) < 4:
160 return None, 4, msg
161 length,type = struct.unpack("!HH",msg)
162 if len(msg) >= 4+length:
163 return type,msg[4:4+length],msg[4+length:]
164 else:
165 return None,4+length,msg
167 def _minLengthToPack(bytes):
168 whole,left = divmod(bytes,65535)
169 if left:
170 return whole*(65535+4)+4+left
171 else:
172 return whole*(65535+4)
174 def unpack_msg(msg):
175 "returns as for _unpack_msg"
176 tp,body,rest = _unpack_msg(msg)
177 if tp != MSG_TYPE.FRAGMENTHEADER:
178 return tp, body, rest
180 if len(body) < 6:
181 raise ProtocolError("FRAGMENTHEADER message too short")
183 realType,realLength = struct.unpack("!HL", body[:6])
185 # Okay; could the message _possibly_ be here?
186 minLength = _minLengthToPack(realLength+6)
187 if len(msg) < minLength:
188 return None, minLength, msg
190 # Okay; optimistically try to build up the msg.
191 soFar = [ body[6:] ]
192 lenSoFarLen = len(body)-6
193 while len(rest)>=4 and lenSoFar < realLength:
194 ln, tp = struct.unpack("!HH", rest[:4])
195 if tp != MSG_TYPE.FRAGMENT:
196 raise ProtocolError("Missing FRAGMENT message")
197 soFar.append(rest[4:4+ln])
198 lenSoFar += ln
199 if 4+ln > len(rest):
200 rest = ""
201 leftInPacket = 4+ln-len(rest)
202 else:
203 rest = rest[4+ln:]
204 leftInPacket=0
206 if lenSoFar == realLength:
207 return realType, "".join(soFar), rest
208 elif lenSoFar > realLength:
209 raise ProtocolError("Bad fragmentation: message longer than declared")
210 else:
211 inOtherPackets = realLength-lenSoFar-leftInPacket
212 minLength = _minLengthToPack(inOtherPackets)
213 return None, len(msg)+leftInPacket+inOtherPackets, msg
215 def _receive_msg(s):
216 body = ""
217 header = s.recv(4)
218 length,type = struct.unpack("!HH",header)
219 if length:
220 while length > len(body):
221 body += s.recv(length-len(body))
222 return length,type,body
224 def receive_message(s):
225 length, tp, body = _receive_msg(s)
226 if tp != MSG_TYPE.FRAGMENTHEADER:
227 return length, tp, body
228 if length < 6:
229 raise ProtocolError("FRAGMENTHEADER message too short")
230 realType,realLength = struct.unpack("!HL", body[:6])
231 data = [ body[6:] ]
232 soFar = len(data[0])
233 while 1:
234 length, tp, body = _receive_msg(s)
235 if tp != MSG_TYPE.FRAGMENT:
236 raise ProtocolError("Missing FRAGMENT message")
237 soFar += length
238 data.append(body)
239 if soFar == realLength:
240 return realLength, realType, "".join(data)
241 elif soFar > realLengtH:
242 raise ProtocolError("FRAGMENT message too long!")
244 _event_handler = None
245 def receive_reply(s, expected=None):
246 while 1:
247 _, tp, body = receive_message(s)
248 if tp == MSG_TYPE.EVENT:
249 if _event_handler is not None:
250 _event_handler(body)
251 elif tp == MSG_TYPE.ERROR:
252 if len(body)<2:
253 raise ProtocolError("(Truncated error message)")
254 errCode, = struct.unpack("!H", body[:2])
255 raise ErrorReply((errCode,
256 ERR_CODES.get(errCode,"[unrecognized]"),
257 body[2:]))
258 elif (expected is not None) and (tp not in expected):
259 raise ProtocolError("Unexpected message type 0x%04x"%tp)
260 else:
261 return tp, body
263 def pack_message(type, body=""):
264 length = len(body)
265 if length < 65536:
266 reqheader = struct.pack("!HH", length, type)
267 return "%s%s"%(reqheader,body)
269 fragheader = struct.pack("!HHHL",
270 65535, MSG_TYPE.FRAGMENTHEADER, type, length)
271 msgs = [ fragheader, body[:65535-6] ]
272 body = body[65535-6:]
273 while body:
274 if len(body) > 65535:
275 fl = 65535
276 else:
277 fl = len(body)
278 fragheader = struct.pack("!HH", MSG_TYPE.FRAGMENT, fl)
279 msgs.append(fragheader)
280 msgs.append(body[:fl])
281 body = body[fl:]
283 return "".join(msgs)
285 def send_message(s, type, body=""):
286 s.sendall(pack_message(type, body))
288 def authenticate(s):
289 send_message(s,MSG_TYPE.AUTH)
290 type,body = receive_reply(s)
291 return
293 def _parseKV(body,sep=" ",term="\n"):
294 res = []
295 for line in body.split(term):
296 if not line: continue
297 print repr(line)
298 k, v = line.split(sep,1)
299 res.append((k,v))
300 return res
302 def get_option(s,name):
303 send_message(s,MSG_TYPE.GETCONF,name)
304 tp,body = receive_reply(s,[MSG_TYPE.CONFVALUE])
305 return _parseKV(body)
307 def set_option(s,msg):
308 send_message(s,MSG_TYPE.SETCONF,msg)
309 tp,body = receive_reply(s,[MSG_TYPE.DONE])
311 def get_info(s,name):
312 send_message(s,MSG_TYPE.GETINFO,name)
313 tp,body = receive_reply(s,[MSG_TYPE.INFOVALUE])
314 kvs = body.split("\0")
315 d = {}
316 for i in xrange(0,len(kvs)-1,2):
317 d[kvs[i]] = kvs[i+1]
318 return d
320 def set_events(s,events):
321 send_message(s,MSG_TYPE.SETEVENTS,
322 "".join([struct.pack("!H", event) for event in events]))
323 type,body = receive_reply(s,[MSG_TYPE.DONE])
324 return
326 def save_conf(s):
327 send_message(s,MSG_TYPE.SAVECONF)
328 receive_reply(s,[MSG_TYPE.DONE])
330 def send_signal(s, sig):
331 send_message(s,MSG_TYPE.SIGNAL,struct.pack("B",sig))
332 receive_reply(s,[MSG_TYPE.DONE])
334 def map_address(s, kv):
335 msg = [ "%s %s\n"%(k,v) for k,v in kv ]
336 send_message(s,MSG_TYPE.MAPADDRESS,"".join(msg))
337 tp, body = receive_reply(s,[MSG_TYPE.DONE])
338 return _parseKV(body)
340 def extend_circuit(s, circid, hops):
341 msg = struct.pack("!L",circid) + ",".join(hops) + "\0"
342 send_message(s,MSG_TYPE.EXTENDCIRCUIT,msg)
343 tp, body = receive_reply(s,[MSG_TYPE.DONE])
344 if len(body) != 4:
345 raise ProtocolError("Extendcircuit reply too short or long")
346 return struct.unpack("!L",body)[0]
348 def redirect_stream(s, streamid, newtarget):
349 msg = struct.pack("!L",streamid) + newtarget + "\0"
350 send_message(s,MSG_TYPE.REDIRECTSTREAM,msg)
351 tp,body = receive_reply(s,[MSG_TYPE.DONE])
353 def attach_stream(s, streamid, circid):
354 msg = struct.pack("!LL",streamid, circid)
355 send_message(s,MSG_TYPE.ATTACHSTREAM,msg)
356 tp,body = receive_reply(s,[MSG_TYPE.DONE])
358 def close_stream(s, streamid, reason=0, flags=0):
359 msg = struct.pack("!LBB",streamid,reason,flags)
360 send_message(s,MSG_TYPE.CLOSESTREAM,msg)
361 tp,body = receive_reply(s,[MSG_TYPE.DONE])
363 def close_circuit(s, circid, flags=0):
364 msg = struct.pack("!LB",circid,flags)
365 send_message(s,MSG_TYPE.CLOSECIRCUIT,msg)
366 tp,body = receive_reply(s,[MSG_TYPE.DONE])
368 def post_descriptor(s, descriptor):
369 send_message(s,MSG_TYPE.POSTDESCRIPTOR,descriptor)
370 tp,body = receive_reply(s,[MSG_TYPE.DONE])
372 def _unterminate(s):
373 if s[-1] == '\0':
374 return s[:-1]
375 else:
376 return s
378 def unpack_event(body):
379 if len(body)<2:
380 raise ProtocolError("EVENT body too short.")
381 evtype, = struct.unpack("!H", body[:2])
382 body = body[2:]
383 if evtype == EVENT_TYPE.CIRCSTATUS:
384 if len(body)<5:
385 raise ProtocolError("CIRCUITSTATUS event too short.")
386 status,ident = struct.unpack("!BL", body[:5])
387 path = _unterminate(body[5:]).split(",")
388 args = status, ident, path
389 elif evtype == EVENT_TYPE.STREAMSTATUS:
390 if len(body)<5:
391 raise ProtocolError("CIRCUITSTATUS event too short.")
392 status,ident = struct.unpack("!BL", body[:5])
393 target = _unterminate(body[5:])
394 args = status, ident, target
395 elif evtype == EVENT_TYPE.ORCONNSTATUS:
396 if len(body)<2:
397 raise ProtocolError("CIRCUITSTATUS event too short.")
398 status = ord(body[0])
399 target = _unterminate(body[1:])
400 args = status, target
401 elif evtype == EVENT_TYPE.BANDWIDTH:
402 if len(body)<8:
403 raise ProtocolError("BANDWIDTH event too short.")
404 read, written = struct.unpack("!LL",body[:8])
405 args = read, written
406 elif evtype == EVENT_TYPE.OBSOLETE_LOG:
407 args = (_unterminate(body),)
408 elif evtype == EVENT_TYPE.NEWDESC:
409 args = (_unterminate(body).split(","),)
410 elif EVENT_TYPE.DEBUG_MSG <= evtype <= EVENT_TYPE.ERR_MSG:
411 args = (EVENT_TYPE.nameOf(evtype), _unterminate(body))
412 else:
413 args = (body,)
415 return evtype, args
417 def listen_for_events(s):
418 while(1):
419 _,type,body = receive_message(s)
420 print unpack_event(body)
421 return
423 def do_main_loop(host,port):
424 print "host is %s:%d"%(host,port)
425 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
426 s.connect((host,port))
427 authenticate(s)
428 print "nick",`get_option(s,"nickname")`
429 print get_option(s,"DirFetchPeriod\n")
430 print `get_info(s,"version")`
431 #print `get_info(s,"desc/name/moria1")`
432 print `get_info(s,"network-status")`
433 print `get_info(s,"addr-mappings/all")`
434 print `get_info(s,"addr-mappings/config")`
435 print `get_info(s,"addr-mappings/cache")`
436 print `get_info(s,"addr-mappings/control")`
437 print `map_address(s, [("0.0.0.0", "Foobar.com"),
438 ("1.2.3.4", "foobaz.com"),
439 ("frebnitz.com", "5.6.7.8"),
440 (".", "abacinator.onion")])`
441 print `extend_circuit(s,0,["moria1"])`
442 print '========'
443 #print `extend_circuit(s,0,[""])`
444 print '========'
445 #send_signal(s,1)
446 #save_conf(s)
448 #set_option(s,"1")
449 #set_option(s,"bandwidthburstbytes 100000")
450 #set_option(s,"runasdaemon 1")
451 #set_events(s,[EVENT_TYPE.WARN])
452 set_events(s,[EVENT_TYPE.OBSOLETE_LOG])
454 listen_for_events(s)
456 return
458 if __name__ == '__main__':
459 if len(sys.argv) != 2:
460 print "Syntax: TorControl.py torhost:torport"
461 sys.exit(0)
462 sh,sp = parseHostAndPort(sys.argv[1])
463 do_main_loop(sh,sp)