summaryrefslogtreecommitdiff
path: root/contrib/tor-control.py
blob: 72333a4c3e2070c84ceeb61ee55a2de2948bd47d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#!/usr/bin/python2
#$Id$

import socket
import struct
import sys

MSG_TYPE_ERROR     = 0x0000
MSG_TYPE_DONE      = 0x0001
MSG_TYPE_SETCONF   = 0x0002
MSG_TYPE_GETCONF   = 0x0003
MSG_TYPE_CONFVALUE = 0x0004
MSG_TYPE_SETEVENTS = 0x0005
MSG_TYPE_EVENT     = 0x0006
MSG_TYPE_AUTH      = 0x0007
MSG_TYPE_SAVECONF  = 0x0008
MSG_TYPE_SIGNAL    = 0x0009
MSG_TYPE_MAPADDRESS     = 0x000A
MSG_TYPE_GETINFO        = 0x000B
MSG_TYPE_INFOVALUE      = 0x000C
MSG_TYPE_EXTENDCIRCUIT  = 0x000D
MSG_TYPE_ATTACHSTREAM   = 0x000E
MSG_TYPE_POSTDESCRIPTOR = 0x000F
MSG_TYPE_FRAGMENTHEADER = 0x0010
MSG_TYPE_FRAGMENT       = 0x0011
MSG_TYPE_REDIRECTSTREAM = 0x0012
MSG_TYPE_CLOSESTREAM    = 0x0013
MSG_TYPE_CLOSECIRCUIT   = 0x0014

EVENT_TYPE_CIRCSTATUS   = 0x0001
EVENT_TYPE_STREAMSTATUS = 0x0002
EVENT_TYPE_ORCONNSTATUS = 0x0003
EVENT_TYPE_BANDWIDTH    = 0x0004
EVENT_TYPE_WARN         = 0x0005
EVENT_TYPE_NEWDESC      = 0x0006

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 _receive_msg(s):
  body = ""
  header = s.recv(4)
  length,type = struct.unpack("!HH",header)
  if length:
    body = s.recv(length)
  return length,type,body

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,"desc/name/moria1")`
  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)
  #save_conf(s)


  #set_option(s,"1")
  #set_option(s,"bandwidthburstbytes 100000")
  #set_option(s,"runasdaemon 1")
  #set_events(s,[EVENT_TYPE_WARN])
  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)