]>
Commit | Line | Data |
---|---|---|
eb5f63fe WL |
1 | #include "torcontrol.h" |
2 | #include "utilstrencodings.h" | |
3 | #include "net.h" | |
4 | #include "util.h" | |
975dc649 | 5 | #include "crypto/hmac_sha256.h" |
eb5f63fe WL |
6 | |
7 | #include <vector> | |
8 | #include <deque> | |
9 | #include <set> | |
10 | #include <stdlib.h> | |
11 | ||
12 | #include <boost/function.hpp> | |
13 | #include <boost/bind.hpp> | |
14 | #include <boost/signals2/signal.hpp> | |
15 | #include <boost/foreach.hpp> | |
16 | #include <boost/algorithm/string/predicate.hpp> | |
17 | #include <boost/algorithm/string/split.hpp> | |
18 | #include <boost/algorithm/string/classification.hpp> | |
975dc649 | 19 | #include <boost/algorithm/string/replace.hpp> |
eb5f63fe WL |
20 | |
21 | #include <event2/bufferevent.h> | |
22 | #include <event2/buffer.h> | |
23 | #include <event2/util.h> | |
24 | #include <event2/event.h> | |
975dc649 | 25 | #include <event2/thread.h> |
eb5f63fe | 26 | |
975dc649 | 27 | /** Default control port */ |
eb5f63fe | 28 | const std::string DEFAULT_TOR_CONTROL = "127.0.0.1:9051"; |
975dc649 WL |
29 | /** Tor cookie size (from control-spec.txt) */ |
30 | static const int TOR_COOKIE_SIZE = 32; | |
31 | /** Size of client/server nonce for SAFECOOKIE */ | |
32 | static const int TOR_NONCE_SIZE = 32; | |
33 | /** For computing serverHash in SAFECOOKIE */ | |
34 | static const std::string TOR_SAFE_SERVERKEY = "Tor safe cookie authentication server-to-controller hash"; | |
35 | /** For computing clientHash in SAFECOOKIE */ | |
36 | static const std::string TOR_SAFE_CLIENTKEY = "Tor safe cookie authentication controller-to-server hash"; | |
37 | /** Exponential backoff configuration - initial timeout in seconds */ | |
38 | static const float RECONNECT_TIMEOUT_START = 1.0; | |
39 | /** Exponential backoff configuration - growth factor */ | |
40 | static const float RECONNECT_TIMEOUT_EXP = 1.5; | |
41 | /** Maximum length for lines received on TorControlConnection. | |
42 | * tor-control-spec.txt mentions that there is explicitly no limit defined to line length, | |
43 | * this is belt-and-suspenders sanity limit to prevent memory exhaustion. | |
44 | */ | |
45 | static const int MAX_LINE_LENGTH = 100000; | |
eb5f63fe WL |
46 | |
47 | /****** Low-level TorControlConnection ********/ | |
48 | ||
49 | /** Reply from Tor, can be single or multi-line */ | |
50 | class TorControlReply | |
51 | { | |
52 | public: | |
53 | TorControlReply() { Clear(); } | |
54 | ||
55 | int code; | |
56 | std::vector<std::string> lines; | |
57 | ||
58 | void Clear() | |
59 | { | |
60 | code = 0; | |
61 | lines.clear(); | |
62 | } | |
63 | }; | |
64 | ||
65 | /** Low-level handling for Tor control connection. | |
66 | * Speaks the SMTP-like protocol as defined in torspec/control-spec.txt | |
67 | */ | |
68 | class TorControlConnection | |
69 | { | |
70 | public: | |
71 | typedef boost::function<void(TorControlConnection&)> ConnectionCB; | |
72 | typedef boost::function<void(TorControlConnection &,const TorControlReply &)> ReplyHandlerCB; | |
73 | ||
74 | /** Create a new TorControlConnection. | |
75 | */ | |
76 | TorControlConnection(struct event_base *base); | |
77 | ~TorControlConnection(); | |
78 | ||
79 | /** | |
80 | * Connect to a Tor control port. | |
81 | * target is address of the form host:port. | |
82 | * connected is the handler that is called when connection is succesfully established. | |
83 | * disconnected is a handler that is called when the connection is broken. | |
84 | * Return true on success. | |
85 | */ | |
86 | bool Connect(const std::string &target, const ConnectionCB& connected, const ConnectionCB& disconnected); | |
87 | ||
88 | /** | |
89 | * Disconnect from Tor control port. | |
90 | */ | |
91 | bool Disconnect(); | |
92 | ||
93 | /** Send a command, register a handler for the reply. | |
94 | * A trailing CRLF is automatically added. | |
95 | * Return true on success. | |
96 | */ | |
97 | bool Command(const std::string &cmd, const ReplyHandlerCB& reply_handler); | |
98 | ||
99 | /** Response handlers for async replies */ | |
100 | boost::signals2::signal<void(TorControlConnection &,const TorControlReply &)> async_handler; | |
101 | private: | |
102 | /** Callback when ready for use */ | |
103 | boost::function<void(TorControlConnection&)> connected; | |
104 | /** Callback when connection lost */ | |
105 | boost::function<void(TorControlConnection&)> disconnected; | |
106 | /** Libevent event base */ | |
107 | struct event_base *base; | |
108 | /** Connection to control socket */ | |
109 | struct bufferevent *b_conn; | |
110 | /** Message being received */ | |
111 | TorControlReply message; | |
112 | /** Response handlers */ | |
113 | std::deque<ReplyHandlerCB> reply_handlers; | |
114 | ||
115 | /** Libevent handlers: internal */ | |
116 | static void readcb(struct bufferevent *bev, void *ctx); | |
117 | static void eventcb(struct bufferevent *bev, short what, void *ctx); | |
118 | }; | |
119 | ||
120 | TorControlConnection::TorControlConnection(struct event_base *base): | |
121 | base(base), b_conn(0) | |
122 | { | |
123 | } | |
124 | ||
125 | TorControlConnection::~TorControlConnection() | |
126 | { | |
127 | if (b_conn) | |
128 | bufferevent_free(b_conn); | |
129 | } | |
130 | ||
131 | void TorControlConnection::readcb(struct bufferevent *bev, void *ctx) | |
132 | { | |
133 | TorControlConnection *self = (TorControlConnection*)ctx; | |
134 | struct evbuffer *input = bufferevent_get_input(bev); | |
135 | size_t n_read_out = 0; | |
136 | char *line; | |
137 | assert(input); | |
138 | // If there is not a whole line to read, evbuffer_readln returns NULL | |
139 | while((line = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF)) != NULL) | |
140 | { | |
141 | std::string s(line, n_read_out); | |
142 | free(line); | |
143 | if (s.size() < 4) // Short line | |
144 | continue; | |
145 | // <status>(-|+| )<data><CRLF> | |
975dc649 | 146 | self->message.code = atoi(s.substr(0,3)); |
eb5f63fe WL |
147 | self->message.lines.push_back(s.substr(4)); |
148 | char ch = s[3]; // '-','+' or ' ' | |
149 | if (ch == ' ') { | |
150 | // Final line, dispatch reply and clean up | |
151 | if (self->message.code >= 600) { | |
152 | // Dispatch async notifications to async handler | |
153 | // Synchronous and asynchronous messages are never interleaved | |
154 | self->async_handler(*self, self->message); | |
155 | } else { | |
156 | if (!self->reply_handlers.empty()) { | |
157 | // Invoke reply handler with message | |
158 | self->reply_handlers.front()(*self, self->message); | |
159 | self->reply_handlers.pop_front(); | |
160 | } else { | |
975dc649 | 161 | LogPrint("tor", "tor: Received unexpected sync reply %i\n", self->message.code); |
eb5f63fe WL |
162 | } |
163 | } | |
164 | self->message.Clear(); | |
165 | } | |
166 | } | |
975dc649 WL |
167 | // Check for size of buffer - protect against memory exhaustion with very long lines |
168 | // Do this after evbuffer_readln to make sure all full lines have been | |
169 | // removed from the buffer. Everything left is an incomplete line. | |
170 | if (evbuffer_get_length(input) > MAX_LINE_LENGTH) { | |
171 | LogPrintf("tor: Disconnecting because MAX_LINE_LENGTH exceeded\n"); | |
172 | self->Disconnect(); | |
173 | } | |
eb5f63fe WL |
174 | } |
175 | ||
176 | void TorControlConnection::eventcb(struct bufferevent *bev, short what, void *ctx) | |
177 | { | |
178 | TorControlConnection *self = (TorControlConnection*)ctx; | |
179 | if (what & BEV_EVENT_CONNECTED) { | |
975dc649 | 180 | LogPrint("tor", "tor: Succesfully connected!\n"); |
eb5f63fe WL |
181 | self->connected(*self); |
182 | } else if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { | |
183 | if (what & BEV_EVENT_ERROR) | |
975dc649 | 184 | LogPrint("tor", "tor: Error connecting to Tor control socket\n"); |
eb5f63fe | 185 | else |
975dc649 | 186 | LogPrint("tor", "tor: End of stream\n"); |
eb5f63fe WL |
187 | self->Disconnect(); |
188 | self->disconnected(*self); | |
189 | } | |
190 | } | |
191 | ||
192 | bool TorControlConnection::Connect(const std::string &target, const ConnectionCB& connected, const ConnectionCB& disconnected) | |
193 | { | |
194 | if (b_conn) | |
195 | Disconnect(); | |
196 | // Parse target address:port | |
197 | struct sockaddr_storage connect_to_addr; | |
198 | int connect_to_addrlen = sizeof(connect_to_addr); | |
199 | if (evutil_parse_sockaddr_port(target.c_str(), | |
200 | (struct sockaddr*)&connect_to_addr, &connect_to_addrlen)<0) { | |
975dc649 | 201 | LogPrintf("tor: Error parsing socket address %s\n", target); |
eb5f63fe WL |
202 | return false; |
203 | } | |
204 | ||
205 | // Create a new socket, set up callbacks and enable notification bits | |
206 | b_conn = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); | |
207 | if (!b_conn) | |
208 | return false; | |
209 | bufferevent_setcb(b_conn, TorControlConnection::readcb, NULL, TorControlConnection::eventcb, this); | |
210 | bufferevent_enable(b_conn, EV_READ|EV_WRITE); | |
211 | this->connected = connected; | |
212 | this->disconnected = disconnected; | |
213 | ||
214 | // Finally, connect to target | |
215 | if (bufferevent_socket_connect(b_conn, (struct sockaddr*)&connect_to_addr, connect_to_addrlen) < 0) { | |
975dc649 | 216 | LogPrintf("tor: Error connecting to address %s\n", target); |
eb5f63fe WL |
217 | return false; |
218 | } | |
219 | return true; | |
220 | } | |
221 | ||
222 | bool TorControlConnection::Disconnect() | |
223 | { | |
224 | if (b_conn) | |
225 | bufferevent_free(b_conn); | |
226 | b_conn = 0; | |
227 | return true; | |
228 | } | |
229 | ||
230 | bool TorControlConnection::Command(const std::string &cmd, const ReplyHandlerCB& reply_handler) | |
231 | { | |
232 | if (!b_conn) | |
233 | return false; | |
234 | struct evbuffer *buf = bufferevent_get_output(b_conn); | |
235 | if (!buf) | |
236 | return false; | |
237 | evbuffer_add(buf, cmd.data(), cmd.size()); | |
238 | evbuffer_add(buf, "\r\n", 2); | |
239 | reply_handlers.push_back(reply_handler); | |
240 | return true; | |
241 | } | |
242 | ||
243 | /****** General parsing utilities ********/ | |
244 | ||
245 | /* Split reply line in the form 'AUTH METHODS=...' into a type | |
246 | * 'AUTH' and arguments 'METHODS=...'. | |
247 | */ | |
248 | static std::pair<std::string,std::string> SplitTorReplyLine(const std::string &s) | |
249 | { | |
250 | size_t ptr=0; | |
251 | std::string type; | |
252 | while (ptr < s.size() && s[ptr] != ' ') { | |
253 | type.push_back(s[ptr]); | |
254 | ++ptr; | |
255 | } | |
256 | if (ptr < s.size()) | |
257 | ++ptr; // skip ' ' | |
258 | return make_pair(type, s.substr(ptr)); | |
259 | } | |
260 | ||
261 | /** Parse reply arguments in the form 'METHODS=COOKIE,SAFECOOKIE COOKIEFILE=".../control_auth_cookie"'. | |
262 | */ | |
263 | static std::map<std::string,std::string> ParseTorReplyMapping(const std::string &s) | |
264 | { | |
265 | std::map<std::string,std::string> mapping; | |
266 | size_t ptr=0; | |
267 | while (ptr < s.size()) { | |
268 | std::string key, value; | |
269 | while (ptr < s.size() && s[ptr] != '=') { | |
270 | key.push_back(s[ptr]); | |
271 | ++ptr; | |
272 | } | |
273 | if (ptr == s.size()) // unexpected end of line | |
274 | return std::map<std::string,std::string>(); | |
275 | ++ptr; // skip '=' | |
276 | if (ptr < s.size() && s[ptr] == '"') { // Quoted string | |
277 | ++ptr; // skip '=' | |
278 | bool escape_next = false; | |
279 | while (ptr < s.size() && (!escape_next && s[ptr] != '"')) { | |
280 | escape_next = (s[ptr] == '\\'); | |
281 | value.push_back(s[ptr]); | |
282 | ++ptr; | |
283 | } | |
284 | if (ptr == s.size()) // unexpected end of line | |
285 | return std::map<std::string,std::string>(); | |
286 | ++ptr; // skip closing '"' | |
287 | /* TODO: unescape value - according to the spec this depends on the | |
288 | * context, some strings use C-LogPrintf style escape codes, some | |
289 | * don't. So may be better handled at the call site. | |
290 | */ | |
291 | } else { // Unquoted value. Note that values can contain '=' at will, just no spaces | |
292 | while (ptr < s.size() && s[ptr] != ' ') { | |
293 | value.push_back(s[ptr]); | |
294 | ++ptr; | |
295 | } | |
296 | } | |
297 | if (ptr < s.size() && s[ptr] == ' ') | |
298 | ++ptr; // skip ' ' after key=value | |
299 | mapping[key] = value; | |
300 | } | |
301 | return mapping; | |
302 | } | |
303 | ||
975dc649 WL |
304 | /** Read full contents of a file and return them in a std::string. |
305 | * Returns a pair <status, string>. | |
306 | * If an error occured, status will be false, otherwise status will be true and the data will be returned in string. | |
307 | * | |
308 | * @param maxsize Puts a maximum size limit on the file that is read. If the file is larger than this, truncated data | |
309 | * (with len > maxsize) will be returned. | |
310 | */ | |
311 | static std::pair<bool,std::string> ReadBinaryFile(const std::string &filename, size_t maxsize=std::numeric_limits<size_t>::max()) | |
eb5f63fe WL |
312 | { |
313 | FILE *f = fopen(filename.c_str(), "rb"); | |
314 | if (f == NULL) | |
315 | return std::make_pair(false,""); | |
316 | std::string retval; | |
317 | char buffer[128]; | |
318 | size_t n; | |
975dc649 | 319 | while ((n=fread(buffer, 1, sizeof(buffer), f)) > 0) { |
eb5f63fe | 320 | retval.append(buffer, buffer+n); |
975dc649 WL |
321 | if (retval.size() > maxsize) |
322 | break; | |
323 | } | |
eb5f63fe WL |
324 | fclose(f); |
325 | return std::make_pair(true,retval); | |
326 | } | |
327 | ||
328 | /** Write contents of std::string to a file. | |
329 | * @return true on success. | |
330 | */ | |
331 | static bool WriteBinaryFile(const std::string &filename, const std::string &data) | |
332 | { | |
333 | FILE *f = fopen(filename.c_str(), "wb"); | |
334 | if (f == NULL) | |
335 | return false; | |
975dc649 WL |
336 | if (fwrite(data.data(), 1, data.size(), f) != data.size()) { |
337 | fclose(f); | |
eb5f63fe | 338 | return false; |
975dc649 | 339 | } |
eb5f63fe WL |
340 | fclose(f); |
341 | return true; | |
342 | } | |
343 | ||
344 | /****** Bitcoin specific TorController implementation ********/ | |
345 | ||
346 | /** Controller that connects to Tor control socket, authenticate, then create | |
347 | * and maintain a ephemeral hidden service. | |
348 | */ | |
349 | class TorController | |
350 | { | |
351 | public: | |
352 | TorController(struct event_base* base, const std::string& target); | |
353 | ~TorController(); | |
354 | ||
355 | /** Get name fo file to store private key in */ | |
356 | std::string GetPrivateKeyFile(); | |
357 | ||
358 | /** Reconnect, after getting disconnected */ | |
359 | void Reconnect(); | |
360 | private: | |
361 | struct event_base* base; | |
362 | std::string target; | |
363 | TorControlConnection conn; | |
364 | std::string private_key; | |
365 | std::string service_id; | |
366 | bool reconnect; | |
eb5f63fe WL |
367 | struct event *reconnect_ev; |
368 | float reconnect_timeout; | |
975dc649 WL |
369 | CService service; |
370 | /** Cooie for SAFECOOKIE auth */ | |
371 | std::vector<uint8_t> cookie; | |
372 | /** ClientNonce for SAFECOOKIE auth */ | |
373 | std::vector<uint8_t> clientNonce; | |
eb5f63fe WL |
374 | |
375 | /** Callback for ADD_ONION result */ | |
376 | void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply); | |
377 | /** Callback for AUTHENTICATE result */ | |
378 | void auth_cb(TorControlConnection& conn, const TorControlReply& reply); | |
975dc649 WL |
379 | /** Callback for AUTHCHALLENGE result */ |
380 | void authchallenge_cb(TorControlConnection& conn, const TorControlReply& reply); | |
eb5f63fe WL |
381 | /** Callback for PROTOCOLINFO result */ |
382 | void protocolinfo_cb(TorControlConnection& conn, const TorControlReply& reply); | |
383 | /** Callback after succesful connection */ | |
384 | void connected_cb(TorControlConnection& conn); | |
385 | /** Callback after connection lost or failed connection attempt */ | |
386 | void disconnected_cb(TorControlConnection& conn); | |
387 | ||
eb5f63fe WL |
388 | /** Callback for reconnect timer */ |
389 | static void reconnect_cb(evutil_socket_t fd, short what, void *arg); | |
390 | }; | |
391 | ||
eb5f63fe WL |
392 | TorController::TorController(struct event_base* base, const std::string& target): |
393 | base(base), | |
975dc649 | 394 | target(target), conn(base), reconnect(true), reconnect_ev(0), |
eb5f63fe WL |
395 | reconnect_timeout(RECONNECT_TIMEOUT_START) |
396 | { | |
397 | // Start connection attempts immediately | |
398 | if (!conn.Connect(target, boost::bind(&TorController::connected_cb, this, _1), | |
399 | boost::bind(&TorController::disconnected_cb, this, _1) )) { | |
975dc649 | 400 | LogPrintf("tor: Initiating connection to Tor control port %s failed\n", target); |
eb5f63fe WL |
401 | } |
402 | // Read service private key if cached | |
403 | std::pair<bool,std::string> pkf = ReadBinaryFile(GetPrivateKeyFile()); | |
404 | if (pkf.first) { | |
975dc649 | 405 | LogPrint("tor", "tor: Reading cached private key from %s\n", GetPrivateKeyFile()); |
eb5f63fe WL |
406 | private_key = pkf.second; |
407 | } | |
eb5f63fe WL |
408 | } |
409 | ||
410 | TorController::~TorController() | |
411 | { | |
eb5f63fe WL |
412 | if (reconnect_ev) |
413 | event_del(reconnect_ev); | |
975dc649 WL |
414 | if (service.IsValid()) { |
415 | RemoveLocal(service); | |
416 | } | |
eb5f63fe WL |
417 | } |
418 | ||
419 | void TorController::add_onion_cb(TorControlConnection& conn, const TorControlReply& reply) | |
420 | { | |
421 | if (reply.code == 250) { | |
975dc649 | 422 | LogPrint("tor", "tor: ADD_ONION succesful\n"); |
eb5f63fe WL |
423 | BOOST_FOREACH(const std::string &s, reply.lines) { |
424 | std::map<std::string,std::string> m = ParseTorReplyMapping(s); | |
425 | std::map<std::string,std::string>::iterator i; | |
426 | if ((i = m.find("ServiceID")) != m.end()) | |
427 | service_id = i->second; | |
428 | if ((i = m.find("PrivateKey")) != m.end()) | |
429 | private_key = i->second; | |
430 | } | |
431 | ||
975dc649 WL |
432 | service = CService(service_id+".onion", GetListenPort(), false); |
433 | LogPrintf("tor: Got service ID %s, advertizing service %s\n", service_id, service.ToString()); | |
eb5f63fe | 434 | if (WriteBinaryFile(GetPrivateKeyFile(), private_key)) { |
975dc649 | 435 | LogPrint("tor", "tor: Cached service private key to %s\n", GetPrivateKeyFile()); |
eb5f63fe | 436 | } else { |
975dc649 | 437 | LogPrintf("tor: Error writing service private key to %s\n", GetPrivateKeyFile()); |
eb5f63fe WL |
438 | } |
439 | AddLocal(service, LOCAL_MANUAL); | |
440 | // ... onion requested - keep connection open | |
77e5601e | 441 | } else if (reply.code == 510) { // 510 Unrecognized command |
975dc649 | 442 | LogPrintf("tor: Add onion failed with unrecognized command (You probably need to upgrade Tor)\n"); |
eb5f63fe | 443 | } else { |
975dc649 | 444 | LogPrintf("tor: Add onion failed; error code %d\n", reply.code); |
eb5f63fe WL |
445 | } |
446 | } | |
447 | ||
448 | void TorController::auth_cb(TorControlConnection& conn, const TorControlReply& reply) | |
449 | { | |
450 | if (reply.code == 250) { | |
975dc649 | 451 | LogPrint("tor", "tor: Authentication succesful\n"); |
eb5f63fe WL |
452 | // Finally - now create the service |
453 | if (private_key.empty()) // No private key, generate one | |
454 | private_key = "NEW:BEST"; | |
455 | // Request hidden service, redirect port. | |
456 | // Note that the 'virtual' port doesn't have to be the same as our internal port, but this is just a convenient | |
457 | // choice. TODO; refactor the shutdown sequence some day. | |
458 | conn.Command(strprintf("ADD_ONION %s Port=%i,127.0.0.1:%i", private_key, GetListenPort(), GetListenPort()), | |
459 | boost::bind(&TorController::add_onion_cb, this, _1, _2)); | |
460 | } else { | |
975dc649 WL |
461 | LogPrintf("tor: Authentication failed\n"); |
462 | } | |
463 | } | |
464 | ||
465 | /** Compute Tor SAFECOOKIE response. | |
466 | * | |
467 | * ServerHash is computed as: | |
468 | * HMAC-SHA256("Tor safe cookie authentication server-to-controller hash", | |
469 | * CookieString | ClientNonce | ServerNonce) | |
470 | * (with the HMAC key as its first argument) | |
471 | * | |
472 | * After a controller sends a successful AUTHCHALLENGE command, the | |
473 | * next command sent on the connection must be an AUTHENTICATE command, | |
474 | * and the only authentication string which that AUTHENTICATE command | |
475 | * will accept is: | |
476 | * | |
477 | * HMAC-SHA256("Tor safe cookie authentication controller-to-server hash", | |
478 | * CookieString | ClientNonce | ServerNonce) | |
479 | * | |
480 | */ | |
481 | static std::vector<uint8_t> ComputeResponse(const std::string &key, const std::vector<uint8_t> &cookie, const std::vector<uint8_t> &clientNonce, const std::vector<uint8_t> &serverNonce) | |
482 | { | |
483 | CHMAC_SHA256 computeHash((const uint8_t*)key.data(), key.size()); | |
484 | std::vector<uint8_t> computedHash(CHMAC_SHA256::OUTPUT_SIZE, 0); | |
485 | computeHash.Write(begin_ptr(cookie), cookie.size()); | |
486 | computeHash.Write(begin_ptr(clientNonce), clientNonce.size()); | |
487 | computeHash.Write(begin_ptr(serverNonce), serverNonce.size()); | |
488 | computeHash.Finalize(begin_ptr(computedHash)); | |
489 | return computedHash; | |
490 | } | |
491 | ||
492 | void TorController::authchallenge_cb(TorControlConnection& conn, const TorControlReply& reply) | |
493 | { | |
494 | if (reply.code == 250) { | |
495 | LogPrint("tor", "tor: SAFECOOKIE authentication challenge succesful\n"); | |
496 | std::pair<std::string,std::string> l = SplitTorReplyLine(reply.lines[0]); | |
497 | if (l.first == "AUTHCHALLENGE") { | |
498 | std::map<std::string,std::string> m = ParseTorReplyMapping(l.second); | |
499 | std::vector<uint8_t> serverHash = ParseHex(m["SERVERHASH"]); | |
500 | std::vector<uint8_t> serverNonce = ParseHex(m["SERVERNONCE"]); | |
501 | LogPrint("tor", "tor: AUTHCHALLENGE ServerHash %s ServerNonce %s\n", HexStr(serverHash), HexStr(serverNonce)); | |
502 | if (serverNonce.size() != 32) { | |
503 | LogPrintf("tor: ServerNonce is not 32 bytes, as required by spec\n"); | |
504 | return; | |
505 | } | |
506 | ||
507 | std::vector<uint8_t> computedServerHash = ComputeResponse(TOR_SAFE_SERVERKEY, cookie, clientNonce, serverNonce); | |
508 | if (computedServerHash != serverHash) { | |
509 | LogPrintf("tor: ServerHash %s does not match expected ServerHash %s\n", HexStr(serverHash), HexStr(computedServerHash)); | |
510 | return; | |
511 | } | |
512 | ||
513 | std::vector<uint8_t> computedClientHash = ComputeResponse(TOR_SAFE_CLIENTKEY, cookie, clientNonce, serverNonce); | |
514 | conn.Command("AUTHENTICATE " + HexStr(computedClientHash), boost::bind(&TorController::auth_cb, this, _1, _2)); | |
515 | } else { | |
516 | LogPrintf("tor: Invalid reply to AUTHCHALLENGE\n"); | |
517 | } | |
518 | } else { | |
519 | LogPrintf("tor: SAFECOOKIE authentication challenge failed\n"); | |
eb5f63fe WL |
520 | } |
521 | } | |
522 | ||
523 | void TorController::protocolinfo_cb(TorControlConnection& conn, const TorControlReply& reply) | |
524 | { | |
525 | if (reply.code == 250) { | |
526 | std::set<std::string> methods; | |
527 | std::string cookiefile; | |
528 | /* | |
529 | * 250-AUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE="/home/x/.tor/control_auth_cookie" | |
530 | * 250-AUTH METHODS=NULL | |
531 | * 250-AUTH METHODS=HASHEDPASSWORD | |
532 | */ | |
533 | BOOST_FOREACH(const std::string &s, reply.lines) { | |
534 | std::pair<std::string,std::string> l = SplitTorReplyLine(s); | |
535 | if (l.first == "AUTH") { | |
536 | std::map<std::string,std::string> m = ParseTorReplyMapping(l.second); | |
537 | std::map<std::string,std::string>::iterator i; | |
538 | if ((i = m.find("METHODS")) != m.end()) | |
539 | boost::split(methods, i->second, boost::is_any_of(",")); | |
540 | if ((i = m.find("COOKIEFILE")) != m.end()) | |
541 | cookiefile = i->second; | |
542 | } else if (l.first == "VERSION") { | |
543 | std::map<std::string,std::string> m = ParseTorReplyMapping(l.second); | |
544 | std::map<std::string,std::string>::iterator i; | |
545 | if ((i = m.find("Tor")) != m.end()) { | |
975dc649 | 546 | LogPrint("tor", "tor: Connected to Tor version %s\n", i->second); |
eb5f63fe WL |
547 | } |
548 | } | |
549 | } | |
550 | BOOST_FOREACH(const std::string &s, methods) { | |
975dc649 | 551 | LogPrint("tor", "tor: Supported authentication method: %s\n", s); |
eb5f63fe | 552 | } |
975dc649 | 553 | // Prefer NULL, otherwise SAFECOOKIE. If a password is provided, use HASHEDPASSWORD |
eb5f63fe WL |
554 | /* Authentication: |
555 | * cookie: hex-encoded ~/.tor/control_auth_cookie | |
556 | * password: "password" | |
557 | */ | |
975dc649 | 558 | std::string torpassword = GetArg("-torpassword", ""); |
eb5f63fe | 559 | if (methods.count("NULL")) { |
975dc649 | 560 | LogPrint("tor", "tor: Using NULL authentication\n"); |
eb5f63fe | 561 | conn.Command("AUTHENTICATE", boost::bind(&TorController::auth_cb, this, _1, _2)); |
975dc649 | 562 | } else if (methods.count("SAFECOOKIE")) { |
eb5f63fe | 563 | // Cookie: hexdump -e '32/1 "%02x""\n"' ~/.tor/control_auth_cookie |
975dc649 WL |
564 | LogPrint("tor", "tor: Using SAFECOOKIE authentication, reading cookie authentication from %s\n", cookiefile); |
565 | std::pair<bool,std::string> status_cookie = ReadBinaryFile(cookiefile, TOR_COOKIE_SIZE); | |
566 | if (status_cookie.first && status_cookie.second.size() == TOR_COOKIE_SIZE) { | |
567 | // conn.Command("AUTHENTICATE " + HexStr(status_cookie.second), boost::bind(&TorController::auth_cb, this, _1, _2)); | |
568 | cookie = std::vector<uint8_t>(status_cookie.second.begin(), status_cookie.second.end()); | |
569 | clientNonce = std::vector<uint8_t>(TOR_NONCE_SIZE, 0); | |
570 | GetRandBytes(&clientNonce[0], TOR_NONCE_SIZE); | |
571 | conn.Command("AUTHCHALLENGE SAFECOOKIE " + HexStr(clientNonce), boost::bind(&TorController::authchallenge_cb, this, _1, _2)); | |
eb5f63fe | 572 | } else { |
975dc649 WL |
573 | if (status_cookie.first) { |
574 | LogPrintf("tor: Authentication cookie %s is not exactly %i bytes, as is required by the spec\n", cookiefile, TOR_COOKIE_SIZE); | |
575 | } else { | |
576 | LogPrintf("tor: Authentication cookie %s could not be opened (check permissions)\n", cookiefile); | |
577 | } | |
578 | } | |
579 | } else if (methods.count("HASHEDPASSWORD")) { | |
580 | if (!torpassword.empty()) { | |
581 | LogPrint("tor", "tor: Using HASHEDPASSWORD authentication\n"); | |
582 | boost::replace_all(torpassword, "\"", "\\\""); | |
583 | conn.Command("AUTHENTICATE \"" + torpassword + "\"", boost::bind(&TorController::auth_cb, this, _1, _2)); | |
584 | } else { | |
585 | LogPrintf("tor: Password authentication required, but no password provided with -torpassword\n"); | |
eb5f63fe WL |
586 | } |
587 | } else { | |
975dc649 | 588 | LogPrintf("tor: No supported authentication method\n"); |
eb5f63fe WL |
589 | } |
590 | } else { | |
975dc649 | 591 | LogPrintf("tor: Requesting protocol info failed\n"); |
eb5f63fe WL |
592 | } |
593 | } | |
594 | ||
595 | void TorController::connected_cb(TorControlConnection& conn) | |
596 | { | |
597 | reconnect_timeout = RECONNECT_TIMEOUT_START; | |
598 | // First send a PROTOCOLINFO command to figure out what authentication is expected | |
599 | if (!conn.Command("PROTOCOLINFO 1", boost::bind(&TorController::protocolinfo_cb, this, _1, _2))) | |
975dc649 | 600 | LogPrintf("tor: Error sending initial protocolinfo command\n"); |
eb5f63fe WL |
601 | } |
602 | ||
603 | void TorController::disconnected_cb(TorControlConnection& conn) | |
604 | { | |
975dc649 WL |
605 | // Stop advertizing service when disconnected |
606 | if (service.IsValid()) | |
607 | RemoveLocal(service); | |
608 | service = CService(); | |
eb5f63fe WL |
609 | if (!reconnect) |
610 | return; | |
975dc649 | 611 | LogPrintf("tor: Disconnected from Tor control port %s, trying to reconnect\n", target); |
eb5f63fe WL |
612 | // Single-shot timer for reconnect. Use exponential backoff. |
613 | struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0)); | |
614 | reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); | |
615 | event_add(reconnect_ev, &time); | |
616 | reconnect_timeout *= RECONNECT_TIMEOUT_EXP; | |
617 | } | |
618 | ||
619 | void TorController::Reconnect() | |
620 | { | |
621 | /* Try to reconnect and reestablish if we get booted - for example, Tor | |
622 | * may be restarting. | |
623 | */ | |
624 | if (!conn.Connect(target, boost::bind(&TorController::connected_cb, this, _1), | |
625 | boost::bind(&TorController::disconnected_cb, this, _1) )) { | |
975dc649 | 626 | LogPrintf("tor: Re-initiating connection to Tor control port %s failed\n", target); |
eb5f63fe WL |
627 | } |
628 | } | |
629 | ||
630 | std::string TorController::GetPrivateKeyFile() | |
631 | { | |
632 | return (GetDataDir() / "onion_private_key").string(); | |
633 | } | |
634 | ||
eb5f63fe WL |
635 | void TorController::reconnect_cb(evutil_socket_t fd, short what, void *arg) |
636 | { | |
637 | TorController *self = (TorController*)arg; | |
638 | self->Reconnect(); | |
639 | } | |
640 | ||
641 | /****** Thread ********/ | |
975dc649 WL |
642 | struct event_base *base; |
643 | boost::thread torControlThread; | |
eb5f63fe WL |
644 | |
645 | static void TorControlThread() | |
646 | { | |
eb5f63fe WL |
647 | TorController ctrl(base, GetArg("-torcontrol", DEFAULT_TOR_CONTROL)); |
648 | ||
649 | event_base_dispatch(base); | |
eb5f63fe WL |
650 | } |
651 | ||
652 | void StartTorControl(boost::thread_group& threadGroup, CScheduler& scheduler) | |
653 | { | |
975dc649 WL |
654 | assert(!base); |
655 | #ifdef WIN32 | |
656 | evthread_use_windows_threads(); | |
657 | #else | |
658 | evthread_use_pthreads(); | |
659 | #endif | |
660 | base = event_base_new(); | |
661 | if (!base) { | |
662 | LogPrintf("tor: Unable to create event_base\n"); | |
663 | return; | |
664 | } | |
665 | ||
666 | torControlThread = boost::thread(boost::bind(&TraceThread<void (*)()>, "torcontrol", &TorControlThread)); | |
667 | } | |
668 | ||
669 | void InterruptTorControl() | |
670 | { | |
671 | if (base) { | |
672 | LogPrintf("tor: Thread interrupt\n"); | |
673 | event_base_loopbreak(base); | |
674 | } | |
eb5f63fe WL |
675 | } |
676 | ||
677 | void StopTorControl() | |
678 | { | |
975dc649 WL |
679 | if (base) { |
680 | torControlThread.join(); | |
681 | event_base_free(base); | |
682 | base = 0; | |
683 | } | |
eb5f63fe WL |
684 | } |
685 |