2 # TorControl.py -- Python module to interface with Tor Control interface.
3 # Copyright 2005 Nick Mathewson -- See LICENSE for licensing information.
10 #__all__ = [ "MSG_TYPE", "" ]
13 # Helper: define an ordered dense name-to-number 1-1 mapping.
14 def __init__(self
, start
, names
):
18 setattr(self
,name
,idx
)
19 self
.nameOf
[idx
] = name
22 # Helper: define an ordered sparse name-to-number 1-1 mapping.
23 def __init__(self
, **args
):
24 self
.__dict
__.update(args
)
26 for k
,v
in args
.items():
29 # Message types that client or server can send.
30 MSG_TYPE
= _Enum(0x0000,
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,
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,
84 # Status codes for "STREAMSTATUS" events
85 STREAM_STATUS
= _Enum(0x00,
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.
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."
123 class ProtocolError(TorCtlError
):
124 "Raised on violations in Tor controller protocol"
127 class ErrorReply(TorCtlError
):
131 def parseHostAndPort(h
):
132 host
, port
= "localhost", 9051
139 print "Bad hostname %r"%h
149 def _unpack_msg(msg
):
150 "return None, minLength, body or type,body,rest"
153 length
,type = struct
.unpack("!HH",msg
)
154 if len(msg
) >= 4+length
:
155 return type,msg
[4:4+length
],msg
[4+length
:]
157 return None,4+length
,msg
159 def _minLengthToPack(bytes
):
160 whole
,left
= divmod(bytes
,65535)
162 return whole
*(65535+4)+4+left
164 return whole
*(65535+4)
167 "returns as for _unpack_msg"
168 tp
,body
,rest
= _unpack_msg(msg
)
169 if tp
!= MSG_TYPE
.FRAGMENTHEADER
:
170 return tp
, body
, rest
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.
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
])
193 leftInPacket
= 4+ln
-len(rest
)
198 if lenSoFar
== realLength
:
199 return realType
, "".join(soFar
), rest
200 elif lenSoFar
> realLength
:
201 raise ProtocolError("Bad fragmentation: message longer than declared")
203 inOtherPackets
= realLength
-lenSoFar
-leftInPacket
204 minLength
= _minLengthToPack(inOtherPackets
)
205 return None, len(msg
)+leftInPacket
+inOtherPackets
, msg
210 length
,type = struct
.unpack("!HH",header
)
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
221 raise ProtocolError("FRAGMENTHEADER message too short")
222 realType
,realLength
= struct
.unpack("!HL", body
[:6])
226 length
, tp
, body
= _receive_msg(s
)
227 if tp
!= MSG_TYPE
.FRAGMENT
:
228 raise ProtocolError("Missing FRAGMENT message")
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):
239 _
, tp
, body
= receive_message(s
)
240 if tp
== MSG_TYPE
.EVENT
:
241 if _event_handler
is not None:
243 elif tp
== MSG_TYPE
.ERROR
:
245 raise ProtocolError("(Truncated error message)")
246 errCode
, = struct
.unpack("!H", body
[:2])
247 raise ErrorReply((errCode
,
248 ERR_CODES
.get(errCode
,"[unrecognized]"),
250 elif (expected
is not None) and (tp
not in expected
):
251 raise ProtocolError("Unexpected message type 0x%04x"%tp
)
255 def pack_message(type, body
=""):
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:]
266 if len(body
) > 65535:
270 fragheader
= struct
.pack("!HH", MSG_TYPE
.FRAGMENT
, fl
)
271 msgs
.append(fragheader
)
272 msgs
.append(body
[:fl
])
277 def send_message(s
, type, body
=""):
278 s
.sendall(pack_message(type, body
))
281 send_message(s
,MSG_TYPE
.AUTH
)
282 type,body
= receive_reply(s
)
285 def _parseKV(body
,sep
=" ",term
="\n"):
287 for line
in body
.split(term
):
288 if not line
: continue
290 k
, v
= line
.split(sep
,1)
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")
308 for i
in xrange(0,len(kvs
)-1,2):
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
])
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
])
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
])
371 def unpack_event(body
):
373 raise ProtocolError("EVENT body too short.")
374 evtype
, = struct
.unpack("!H", body
[:2])
376 if evtype
== EVENT_TYPE
.CIRCSTATUS
:
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
:
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
:
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
:
396 raise ProtocolError("BANDWIDTH event too short.")
397 read
, written
= struct
.unpack("!LL",body
[:8])
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
))
410 def listen_for_events(s
):
412 _
,type,body
= receive_message(s
)
413 print unpack_event(body
)
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
))
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"])`
436 #print `extend_circuit(s,0,[""])`
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
])
452 if __name__
== '__main__':
453 if len(sys
.argv
) != 2:
454 print "Syntax: TorControl.py torhost:torport"
456 sh
,sp
= parseHostAndPort(sys
.argv
[1])