2 # TorControl.py -- Python module to interface with Tor Control interface.
3 # Copyright 2005 Nick Mathewson -- See LICENSE for licensing information.
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.
18 #__all__ = [ "MSG_TYPE", "" ]
21 # Helper: define an ordered dense name-to-number 1-1 mapping.
22 def __init__(self
, start
, names
):
26 setattr(self
,name
,idx
)
27 self
.nameOf
[idx
] = name
30 # Helper: define an ordered sparse name-to-number 1-1 mapping.
31 def __init__(self
, **args
):
32 self
.__dict
__.update(args
)
34 for k
,v
in args
.items():
37 # Message types that client or server can send.
38 MSG_TYPE
= _Enum(0x0000,
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,
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,
92 # Status codes for "STREAMSTATUS" events
93 STREAM_STATUS
= _Enum(0x00,
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.
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."
131 class ProtocolError(TorCtlError
):
132 "Raised on violations in Tor controller protocol"
135 class ErrorReply(TorCtlError
):
139 def parseHostAndPort(h
):
140 host
, port
= "localhost", 9051
147 print "Bad hostname %r"%h
157 def _unpack_msg(msg
):
158 "return None, minLength, body or type,body,rest"
161 length
,type = struct
.unpack("!HH",msg
)
162 if len(msg
) >= 4+length
:
163 return type,msg
[4:4+length
],msg
[4+length
:]
165 return None,4+length
,msg
167 def _minLengthToPack(bytes
):
168 whole
,left
= divmod(bytes
,65535)
170 return whole
*(65535+4)+4+left
172 return whole
*(65535+4)
175 "returns as for _unpack_msg"
176 tp
,body
,rest
= _unpack_msg(msg
)
177 if tp
!= MSG_TYPE
.FRAGMENTHEADER
:
178 return tp
, body
, rest
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.
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
])
201 leftInPacket
= 4+ln
-len(rest
)
206 if lenSoFar
== realLength
:
207 return realType
, "".join(soFar
), rest
208 elif lenSoFar
> realLength
:
209 raise ProtocolError("Bad fragmentation: message longer than declared")
211 inOtherPackets
= realLength
-lenSoFar
-leftInPacket
212 minLength
= _minLengthToPack(inOtherPackets
)
213 return None, len(msg
)+leftInPacket
+inOtherPackets
, msg
218 length
,type = struct
.unpack("!HH",header
)
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
229 raise ProtocolError("FRAGMENTHEADER message too short")
230 realType
,realLength
= struct
.unpack("!HL", body
[:6])
234 length
, tp
, body
= _receive_msg(s
)
235 if tp
!= MSG_TYPE
.FRAGMENT
:
236 raise ProtocolError("Missing FRAGMENT message")
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):
247 _
, tp
, body
= receive_message(s
)
248 if tp
== MSG_TYPE
.EVENT
:
249 if _event_handler
is not None:
251 elif tp
== MSG_TYPE
.ERROR
:
253 raise ProtocolError("(Truncated error message)")
254 errCode
, = struct
.unpack("!H", body
[:2])
255 raise ErrorReply((errCode
,
256 ERR_CODES
.get(errCode
,"[unrecognized]"),
258 elif (expected
is not None) and (tp
not in expected
):
259 raise ProtocolError("Unexpected message type 0x%04x"%tp
)
263 def pack_message(type, body
=""):
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:]
274 if len(body
) > 65535:
278 fragheader
= struct
.pack("!HH", MSG_TYPE
.FRAGMENT
, fl
)
279 msgs
.append(fragheader
)
280 msgs
.append(body
[:fl
])
285 def send_message(s
, type, body
=""):
286 s
.sendall(pack_message(type, body
))
289 send_message(s
,MSG_TYPE
.AUTH
)
290 type,body
= receive_reply(s
)
293 def _parseKV(body
,sep
=" ",term
="\n"):
295 for line
in body
.split(term
):
296 if not line
: continue
298 k
, v
= line
.split(sep
,1)
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")
316 for i
in xrange(0,len(kvs
)-1,2):
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
])
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
])
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
])
378 def unpack_event(body
):
380 raise ProtocolError("EVENT body too short.")
381 evtype
, = struct
.unpack("!H", body
[:2])
383 if evtype
== EVENT_TYPE
.CIRCSTATUS
:
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
:
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
:
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
:
403 raise ProtocolError("BANDWIDTH event too short.")
404 read
, written
= struct
.unpack("!LL",body
[:8])
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
))
417 def listen_for_events(s
):
419 _
,type,body
= receive_message(s
)
420 print unpack_event(body
)
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
))
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"])`
443 #print `extend_circuit(s,0,[""])`
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
])
458 if __name__
== '__main__':
459 if len(sys
.argv
) != 2:
460 print "Syntax: TorControl.py torhost:torport"
462 sh
,sp
= parseHostAndPort(sys
.argv
[1])