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