upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/tor.cpp
diff options
context:
space:
mode:
authorBitflash <admin@bitflash.network>2026-08-13 14:21:15 -0300
committerBitflash <admin@bitflash.network>2026-08-13 14:21:15 -0300
commit5dec9b16a5b98ed0e24ec7113b867f22be2d0026 (patch)
treee06544e9e7516203bc4309460438a0486b0328e4 /src/tor.cpp
parentc009e7cc0c5093f4c4add741cb9d131d19956871 (diff)
parent1bf78c53404d83638177889590ce199c87af63bc (diff)
Merge pull request 'net: add managed Tor hidden service bootstrap' (#33) from embedded-tor-manager into main
Diffstat (limited to 'src/tor.cpp')
-rw-r--r--src/tor.cpp866
1 files changed, 866 insertions, 0 deletions
diff --git a/src/tor.cpp b/src/tor.cpp
new file mode 100644
index 0000000..e47ee44
--- /dev/null
+++ b/src/tor.cpp
@@ -0,0 +1,866 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3
4#include "headers_core.h"
5#include "tor.h"
6#include "nostr.h"
7#include "proxy.h"
8
9#include <errno.h>
10
11#ifndef _WIN32
12#include <signal.h>
13#include <sys/wait.h>
14#endif
15
16static CCriticalSection cs_managedTor;
17static bool g_fManagedTor = false;
18static bool g_fManagedTorStopping = false;
19static unsigned short g_managedTorSocksPort = 0;
20static unsigned short g_managedTorControlPort = 0;
21static unsigned short g_managedTorP2PPort = 0;
22static std::string g_managedTorPath;
23static std::string g_managedTorDataDir;
24static std::string g_managedTorHiddenServiceDir;
25static std::string g_managedTorOnion;
26static std::string g_managedTorStatus;
27#ifdef _WIN32
28static PROCESS_INFORMATION g_managedTorProcess;
29#else
30static pid_t g_managedTorPid = -1;
31#endif
32
33static std::string PathJoin(const std::string& a, const std::string& b)
34{
35 if (a.empty())
36 return b;
37 char last = a[a.size() - 1];
38 if (last == '/' || last == '\\')
39 return a + b;
40 return a + "/" + b;
41}
42
43static std::string NormalizeTorrcPath(std::string path)
44{
45 for (size_t i = 0; i < path.size(); i++)
46 if (path[i] == '\\')
47 path[i] = '/';
48 return path;
49}
50
51static std::string QuoteTorrcPath(const std::string& path)
52{
53 std::string out = "\"";
54 for (size_t i = 0; i < path.size(); i++)
55 {
56 if (path[i] == '"' || path[i] == '\\')
57 out += '\\';
58 out += path[i];
59 }
60 out += "\"";
61 return out;
62}
63
64static std::string QuoteCommandArg(const std::string& arg)
65{
66 std::string out = "\"";
67 for (size_t i = 0; i < arg.size(); i++)
68 {
69 if (arg[i] == '"')
70 out += '\\';
71 out += arg[i];
72 }
73 out += "\"";
74 return out;
75}
76
77static bool EnsureDir(const std::string& path, std::string& errOut)
78{
79 if (path.empty())
80 {
81 errOut = "directory path is empty";
82 return false;
83 }
84#ifdef _WIN32
85 DWORD attr = GetFileAttributesA(path.c_str());
86 if (attr != INVALID_FILE_ATTRIBUTES)
87 {
88 if (attr & FILE_ATTRIBUTE_DIRECTORY)
89 return true;
90 errOut = strprintf("%s exists and is not a directory", path.c_str());
91 return false;
92 }
93 if (CreateDirectoryA(path.c_str(), NULL))
94 return true;
95 DWORD err = GetLastError();
96 if (err == ERROR_ALREADY_EXISTS)
97 return true;
98 errOut = strprintf("could not create %s (error %lu)", path.c_str(), (unsigned long)err);
99 return false;
100#else
101 struct stat st;
102 if (stat(path.c_str(), &st) == 0)
103 {
104 if (S_ISDIR(st.st_mode))
105 {
106 chmod(path.c_str(), 0700);
107 return true;
108 }
109 errOut = strprintf("%s exists and is not a directory", path.c_str());
110 return false;
111 }
112 if (mkdir(path.c_str(), 0700) == 0)
113 return true;
114 if (errno == EEXIST)
115 {
116 if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode))
117 {
118 chmod(path.c_str(), 0700);
119 return true;
120 }
121 }
122 errOut = strprintf("could not create %s: %s", path.c_str(), strerror(errno));
123 return false;
124#endif
125}
126
127static bool WriteTextFile(const std::string& path, const std::string& data,
128 std::string& errOut)
129{
130 std::string tmp = path + ".tmp";
131 FILE* f = fopen(tmp.c_str(), "wb");
132 if (!f)
133 {
134 errOut = strprintf("could not write %s", tmp.c_str());
135 return false;
136 }
137 bool ok = fwrite(data.data(), 1, data.size(), f) == data.size();
138 if (fclose(f) != 0)
139 ok = false;
140 if (!ok)
141 {
142 remove(tmp.c_str());
143 errOut = strprintf("could not finish writing %s", tmp.c_str());
144 return false;
145 }
146#ifdef _WIN32
147 if (!MoveFileExA(tmp.c_str(), path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
148 {
149 remove(tmp.c_str());
150 errOut = strprintf("could not install %s atomically (error %lu)",
151 path.c_str(), (unsigned long)GetLastError());
152 return false;
153 }
154#else
155 if (rename(tmp.c_str(), path.c_str()) != 0)
156 {
157 remove(tmp.c_str());
158 errOut = strprintf("could not install %s atomically: %s",
159 path.c_str(), strerror(errno));
160 return false;
161 }
162#endif
163 return true;
164}
165
166static bool ReadBinaryFile(const std::string& path, std::vector<unsigned char>& data,
167 std::string& errOut)
168{
169 data.clear();
170 FILE* f = fopen(path.c_str(), "rb");
171 if (!f)
172 {
173 errOut = strprintf("could not read %s", path.c_str());
174 return false;
175 }
176 unsigned char buf[256];
177 for (;;)
178 {
179 size_t n = fread(buf, 1, sizeof(buf), f);
180 if (n > 0)
181 data.insert(data.end(), buf, buf + n);
182 if (n < sizeof(buf))
183 {
184 if (ferror(f))
185 {
186 fclose(f);
187 errOut = strprintf("could not finish reading %s", path.c_str());
188 return false;
189 }
190 break;
191 }
192 if (data.size() > 1024)
193 {
194 fclose(f);
195 errOut = strprintf("%s is unexpectedly large", path.c_str());
196 return false;
197 }
198 }
199 fclose(f);
200 if (data.empty())
201 {
202 errOut = strprintf("%s is empty", path.c_str());
203 return false;
204 }
205 return true;
206}
207
208static void SetSocketTimeouts(SOCKET s, int millis)
209{
210#ifdef _WIN32
211 DWORD timeout = millis;
212 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
213 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
214#else
215 struct timeval tv;
216 tv.tv_sec = millis / 1000;
217 tv.tv_usec = (millis % 1000) * 1000;
218 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
219 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
220#endif
221}
222
223static bool SendAll(SOCKET s, const std::string& data)
224{
225 const char* p = data.data();
226 int left = (int)data.size();
227 while (left > 0)
228 {
229 int n = send(s, p, left, 0);
230 if (n <= 0)
231 return false;
232 p += n;
233 left -= n;
234 }
235 return true;
236}
237
238static bool RecvControlLine(SOCKET s, std::string& line)
239{
240 line.clear();
241 while (line.size() < 4096)
242 {
243 char ch = 0;
244 int n = recv(s, &ch, 1, 0);
245 if (n <= 0)
246 return false;
247 if (ch == '\n')
248 return true;
249 if (ch != '\r')
250 line += ch;
251 }
252 return false;
253}
254
255static SOCKET ConnectLoopback(unsigned short port, std::string& errOut)
256{
257#ifdef _WIN32
258 WSADATA wsadata;
259 int ret = WSAStartup(MAKEWORD(2,2), &wsadata);
260 if (ret != NO_ERROR)
261 {
262 errOut = strprintf("could not start Winsock for Tor control shutdown (WSAStartup returned %d)", ret);
263 return INVALID_SOCKET;
264 }
265#endif
266 SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
267 if (s == INVALID_SOCKET)
268 {
269 errOut = "could not create Tor control socket";
270#ifdef _WIN32
271 WSACleanup();
272#endif
273 return INVALID_SOCKET;
274 }
275 SetSocketTimeouts(s, 3000);
276 struct sockaddr_in addr;
277 memset(&addr, 0, sizeof(addr));
278 addr.sin_family = AF_INET;
279 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
280 addr.sin_port = htons(port);
281 if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) != 0)
282 {
283 closesocket(s);
284 errOut = strprintf("could not connect to Tor control port 127.0.0.1:%u", (unsigned)port);
285#ifdef _WIN32
286 WSACleanup();
287#endif
288 return INVALID_SOCKET;
289 }
290 return s;
291}
292
293static void CloseLoopback(SOCKET s)
294{
295 if (s != INVALID_SOCKET)
296 closesocket(s);
297#ifdef _WIN32
298 WSACleanup();
299#endif
300}
301
302static bool ReadPositiveTorControlReply(SOCKET s, std::string& errOut)
303{
304 std::string line;
305 if (!RecvControlLine(s, line))
306 {
307 errOut = "Tor control port closed before replying";
308 return false;
309 }
310 if (line.size() >= 3 && line.substr(0, 3) == "250")
311 return true;
312 errOut = strprintf("Tor control command failed: %s", line.c_str());
313 return false;
314}
315
316static bool RequestManagedTorShutdown(std::string& errOut)
317{
318 std::string dataDir;
319 unsigned short controlPort = 0;
320 CRITICAL_BLOCK(cs_managedTor)
321 {
322 dataDir = g_managedTorDataDir;
323 controlPort = g_managedTorControlPort;
324 }
325 if (controlPort == 0 || dataDir.empty())
326 {
327 errOut = "Tor control port is not available";
328 return false;
329 }
330
331 std::vector<unsigned char> cookie;
332 if (!ReadBinaryFile(PathJoin(dataDir, "control_auth_cookie"), cookie, errOut))
333 return false;
334
335 SOCKET s = ConnectLoopback(controlPort, errOut);
336 if (s == INVALID_SOCKET)
337 return false;
338
339 bool ok = false;
340 std::string auth = "AUTHENTICATE " + HexStr(cookie.begin(), cookie.end(), false) + "\r\n";
341 if (!SendAll(s, auth) || !ReadPositiveTorControlReply(s, errOut))
342 goto done;
343
344 if (!SendAll(s, "SIGNAL SHUTDOWN\r\n"))
345 {
346 errOut = "could not send Tor shutdown signal";
347 goto done;
348 }
349
350 {
351 std::string reply;
352 if (RecvControlLine(s, reply) && !(reply.size() >= 3 && reply.substr(0, 3) == "250"))
353 {
354 errOut = strprintf("Tor rejected shutdown signal: %s", reply.c_str());
355 goto done;
356 }
357 }
358 ok = true;
359
360done:
361 CloseLoopback(s);
362 return ok;
363}
364
365static bool ReadOneLine(const std::string& path, std::string& out)
366{
367 out.clear();
368 FILE* f = fopen(path.c_str(), "rb");
369 if (!f)
370 return false;
371 char buf[256] = {0};
372 if (!fgets(buf, sizeof(buf), f))
373 {
374 fclose(f);
375 return false;
376 }
377 fclose(f);
378 out = buf;
379 while (!out.empty() && (out[out.size() - 1] == '\n' || out[out.size() - 1] == '\r' ||
380 out[out.size() - 1] == ' ' || out[out.size() - 1] == '\t'))
381 out.erase(out.size() - 1);
382 return !out.empty();
383}
384
385static bool PickLocalPort(unsigned short& portOut, std::string& errOut)
386{
387 portOut = 0;
388 bool ok = false;
389#ifdef _WIN32
390 WSADATA wsadata;
391 int ret = WSAStartup(MAKEWORD(2,2), &wsadata);
392 if (ret != NO_ERROR)
393 {
394 errOut = strprintf("could not start Winsock for local port probe (WSAStartup returned %d)", ret);
395 return false;
396 }
397#endif
398 struct sockaddr_in addr;
399 socklen_t len = sizeof(addr);
400 SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
401 if (s == INVALID_SOCKET)
402 {
403 errOut = "could not create local port probe socket";
404 goto done;
405 }
406 memset(&addr, 0, sizeof(addr));
407 addr.sin_family = AF_INET;
408 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
409 addr.sin_port = 0;
410 if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) != 0)
411 {
412 errOut = "could not bind local port probe socket";
413 goto done;
414 }
415 if (getsockname(s, (struct sockaddr*)&addr, &len) != 0)
416 {
417 errOut = "could not read local port probe socket";
418 goto done;
419 }
420 portOut = ntohs(addr.sin_port);
421 if (portOut == 0)
422 {
423 errOut = "local port probe returned port zero";
424 goto done;
425 }
426 ok = true;
427done:
428 if (s != INVALID_SOCKET)
429 closesocket(s);
430#ifdef _WIN32
431 WSACleanup();
432#endif
433 return ok;
434}
435
436static std::string ExecutableDir()
437{
438#ifdef _WIN32
439 char path[MAX_PATH + 1] = {0};
440 DWORD n = GetModuleFileNameA(NULL, path, MAX_PATH);
441 if (n == 0 || n >= MAX_PATH)
442 return ".";
443 std::string s = path;
444 size_t slash = s.find_last_of("\\/");
445 if (slash == std::string::npos)
446 return ".";
447 return s.substr(0, slash);
448#else
449 char path[4096] = {0};
450 ssize_t n = readlink("/proc/self/exe", path, sizeof(path) - 1);
451 if (n <= 0)
452 return ".";
453 path[n] = 0;
454 std::string s = path;
455 size_t slash = s.find_last_of('/');
456 if (slash == std::string::npos)
457 return ".";
458 return s.substr(0, slash);
459#endif
460}
461
462static bool FileIsExecutableCandidate(const std::string& path)
463{
464#ifdef _WIN32
465 DWORD attr = GetFileAttributesA(path.c_str());
466 return attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY);
467#else
468 return access(path.c_str(), X_OK) == 0;
469#endif
470}
471
472static std::string ResolveTorPath(const std::string& torPathOpt)
473{
474 if (!torPathOpt.empty())
475 return torPathOpt;
476
477 std::vector<std::string> candidates;
478 std::string exeDir = ExecutableDir();
479#ifdef _WIN32
480 candidates.push_back(PathJoin(exeDir, "tor/tor.exe"));
481 candidates.push_back(PathJoin(exeDir, "tor.exe"));
482 candidates.push_back("tor.exe");
483#else
484 candidates.push_back(PathJoin(exeDir, "tor/tor"));
485 candidates.push_back(PathJoin(exeDir, "tor"));
486 candidates.push_back("tor");
487#endif
488 for (size_t i = 0; i < candidates.size(); i++)
489 {
490 if (candidates[i].find('/') == std::string::npos &&
491 candidates[i].find('\\') == std::string::npos)
492 return candidates[i]; // Let the OS search PATH.
493 if (FileIsExecutableCandidate(candidates[i]))
494 return candidates[i];
495 }
496#ifdef _WIN32
497 return "tor.exe";
498#else
499 return "tor";
500#endif
501}
502
503std::string BtfBuildManagedTorrcForTest(const std::string& dataDir,
504 const std::string& hiddenServiceDir,
505 unsigned short socksPort,
506 unsigned short controlPort,
507 unsigned short p2pPort)
508{
509 std::string torData = NormalizeTorrcPath(dataDir);
510 std::string hsDir = NormalizeTorrcPath(hiddenServiceDir);
511 std::string s;
512 s += "# Generated by Bitflash. Edit only while Bitflash is not managing Tor.\n";
513 s += "DataDirectory " + QuoteTorrcPath(torData) + "\n";
514 s += strprintf("SocksPort 127.0.0.1:%u IsolateSOCKSAuth IsolateClientAddr IsolateDestAddr IsolateDestPort\n",
515 (unsigned)socksPort);
516 s += strprintf("ControlPort 127.0.0.1:%u\n", (unsigned)controlPort);
517 s += "CookieAuthentication 1\n";
518 s += "HiddenServiceDir " + QuoteTorrcPath(hsDir) + "\n";
519 s += "HiddenServiceVersion 3\n";
520 s += strprintf("HiddenServicePort %u 127.0.0.1:%u\n",
521 (unsigned)p2pPort, (unsigned)p2pPort);
522 return s;
523}
524
525static bool StartTorProcess(const std::string& torPath, const std::string& torrcPath,
526 std::string& errOut)
527{
528#ifdef _WIN32
529 memset(&g_managedTorProcess, 0, sizeof(g_managedTorProcess));
530 STARTUPINFOA si;
531 memset(&si, 0, sizeof(si));
532 si.cb = sizeof(si);
533 std::string cmd = QuoteCommandArg(torPath) + " -f " + QuoteCommandArg(torrcPath);
534 std::vector<char> mutableCmd(cmd.begin(), cmd.end());
535 mutableCmd.push_back(0);
536 if (!CreateProcessA(NULL, &mutableCmd[0], NULL, NULL, FALSE, CREATE_NO_WINDOW,
537 NULL, NULL, &si, &g_managedTorProcess))
538 {
539 errOut = strprintf("could not start Tor process %s (error %lu)",
540 torPath.c_str(), (unsigned long)GetLastError());
541 return false;
542 }
543 return true;
544#else
545 g_managedTorPid = fork();
546 if (g_managedTorPid < 0)
547 {
548 errOut = strprintf("could not fork Tor process: %s", strerror(errno));
549 return false;
550 }
551 if (g_managedTorPid == 0)
552 {
553 execlp(torPath.c_str(), torPath.c_str(), "-f", torrcPath.c_str(), (char*)NULL);
554 _exit(127);
555 }
556 return true;
557#endif
558}
559
560static bool ManagedTorProcessAlive(bool& fExited, int& nExitCode)
561{
562 fExited = false;
563 nExitCode = 0;
564#ifdef _WIN32
565 HANDLE hProcess = NULL;
566 CRITICAL_BLOCK(cs_managedTor)
567 hProcess = g_managedTorProcess.hProcess;
568 if (!hProcess)
569 return false;
570 DWORD code = 0;
571 if (!GetExitCodeProcess(hProcess, &code))
572 return false;
573 if (code != STILL_ACTIVE)
574 {
575 fExited = true;
576 nExitCode = (int)code;
577 return false;
578 }
579 return true;
580#else
581 pid_t pid = -1;
582 CRITICAL_BLOCK(cs_managedTor)
583 pid = g_managedTorPid;
584 if (pid <= 0)
585 return false;
586 int status = 0;
587 pid_t r = waitpid(pid, &status, WNOHANG);
588 if (r == 0)
589 return true;
590 if (r == pid)
591 {
592 fExited = true;
593 if (WIFEXITED(status))
594 nExitCode = WEXITSTATUS(status);
595 else if (WIFSIGNALED(status))
596 nExitCode = 128 + WTERMSIG(status);
597 CRITICAL_BLOCK(cs_managedTor)
598 if (g_managedTorPid == pid)
599 g_managedTorPid = -1;
600 }
601 return false;
602#endif
603}
604
605static bool ValidateManagedTorStartup(std::string& errOut)
606{
607 Sleep(500);
608 bool fExited = false;
609 int nExitCode = 0;
610 if (ManagedTorProcessAlive(fExited, nExitCode))
611 return true;
612 if (fExited)
613 errOut = strprintf("Tor exited during startup with code %d", nExitCode);
614 else
615 errOut = "Tor process is not running after startup";
616 return false;
617}
618
619static void MarkManagedTorDead(int nExitCode)
620{
621 BtfStopManagedTor();
622 CRITICAL_BLOCK(cs_managedTor)
623 g_managedTorStatus = strprintf("stopped unexpectedly, Tor exit code %d", nExitCode);
624}
625
626static void ThreadManagedTorWatchdog(void*)
627{
628 for (;;)
629 {
630 Sleep(5000);
631 CRITICAL_BLOCK(cs_managedTor)
632 {
633 if (!g_fManagedTor || g_fManagedTorStopping || fShutdown)
634 return;
635 }
636
637 bool fExited = false;
638 int nExitCode = 0;
639 if (!ManagedTorProcessAlive(fExited, nExitCode))
640 {
641 if (fExited)
642 MarkManagedTorDead(nExitCode);
643 else
644 {
645 BtfStopManagedTor();
646 CRITICAL_BLOCK(cs_managedTor)
647 g_managedTorStatus = "stopped unexpectedly, Tor process handle is gone";
648 }
649 return;
650 }
651 }
652}
653
654static void ThreadManagedTorHostname(void*)
655{
656 std::string hostnamePath;
657 unsigned short p2pPort = 0;
658 CRITICAL_BLOCK(cs_managedTor)
659 {
660 hostnamePath = PathJoin(g_managedTorHiddenServiceDir, "hostname");
661 p2pPort = g_managedTorP2PPort;
662 }
663
664 for (int i = 0; i < 90 && !fShutdown; i++)
665 {
666 std::string onion;
667 if (ReadOneLine(hostnamePath, onion))
668 {
669 std::string endpoint = strprintf("%s:%u", onion.c_str(), (unsigned)p2pPort);
670 std::string err;
671 if (BtfSetLocalOnionEndpoint(endpoint, err))
672 {
673 CRITICAL_BLOCK(cs_managedTor)
674 {
675 g_managedTorOnion = BtfLocalOnionEndpoint();
676 g_managedTorStatus = strprintf("running, SOCKS5 127.0.0.1:%u, onion %s",
677 (unsigned)g_managedTorSocksPort,
678 g_managedTorOnion.c_str());
679 }
680 printf("Managed Tor hidden service ready: %s\n", BtfLocalOnionEndpoint().c_str());
681 return;
682 }
683 CRITICAL_BLOCK(cs_managedTor)
684 g_managedTorStatus = strprintf("running, but generated onion endpoint was rejected: %s",
685 err.c_str());
686 return;
687 }
688 Sleep(1000);
689 }
690
691 BtfStopManagedTor();
692 CRITICAL_BLOCK(cs_managedTor)
693 g_managedTorStatus = "failed: Tor did not create a hidden service hostname within 90s";
694}
695
696bool BtfStartManagedTor(const std::string& torPathOpt, std::string& errOut)
697{
698 CRITICAL_BLOCK(cs_managedTor)
699 {
700 if (g_fManagedTor)
701 {
702 errOut = "managed Tor is already running";
703 return false;
704 }
705 }
706
707 std::string root = PathJoin(GetAppDir(), "managed-tor");
708 std::string dataDir = PathJoin(root, "data");
709 std::string hsDir = PathJoin(root, "onion-service");
710 std::string torrcPath = PathJoin(root, "torrc");
711 if (!EnsureDir(root, errOut) || !EnsureDir(dataDir, errOut) || !EnsureDir(hsDir, errOut))
712 return false;
713
714 unsigned short socksPort = 0;
715 if (!PickLocalPort(socksPort, errOut))
716 return false;
717 unsigned short controlPort = 0;
718 if (!PickLocalPort(controlPort, errOut))
719 return false;
720
721 unsigned short p2pPort = ntohs(nListenPort);
722 std::string torrc = BtfBuildManagedTorrcForTest(dataDir, hsDir, socksPort,
723 controlPort, p2pPort);
724 if (!WriteTextFile(torrcPath, torrc, errOut))
725 return false;
726
727 std::string torPath = ResolveTorPath(torPathOpt);
728 {
729 CRITICAL_BLOCK(cs_managedTor)
730 {
731 g_fManagedTor = true;
732 g_fManagedTorStopping = false;
733 g_managedTorPath = torPath;
734 g_managedTorDataDir = dataDir;
735 g_managedTorHiddenServiceDir = hsDir;
736 g_managedTorSocksPort = socksPort;
737 g_managedTorControlPort = controlPort;
738 g_managedTorP2PPort = p2pPort;
739 g_managedTorOnion.clear();
740 g_managedTorStatus = strprintf("starting %s, SOCKS5 127.0.0.1:%u",
741 torPath.c_str(), (unsigned)socksPort);
742 }
743 }
744
745 if (!StartTorProcess(torPath, torrcPath, errOut))
746 {
747 CRITICAL_BLOCK(cs_managedTor)
748 {
749 g_fManagedTor = false;
750 g_managedTorStatus = strprintf("failed: %s", errOut.c_str());
751 }
752 return false;
753 }
754 if (!ValidateManagedTorStartup(errOut))
755 {
756 BtfStopManagedTor();
757 CRITICAL_BLOCK(cs_managedTor)
758 g_managedTorStatus = strprintf("failed: %s", errOut.c_str());
759 return false;
760 }
761
762 std::string proxyErr;
763 if (!BtfEnableTorProxy(strprintf("127.0.0.1:%u", (unsigned)socksPort), proxyErr))
764 {
765 BtfStopManagedTor();
766 errOut = strprintf("could not enable managed Tor SOCKS5 proxy: %s", proxyErr.c_str());
767 return false;
768 }
769
770 if (_beginthread(ThreadManagedTorHostname, 0, NULL) == (uintptr_t)-1)
771 {
772 BtfStopManagedTor();
773 errOut = "could not start managed Tor hostname watcher";
774 return false;
775 }
776 if (_beginthread(ThreadManagedTorWatchdog, 0, NULL) == (uintptr_t)-1)
777 {
778 BtfStopManagedTor();
779 errOut = "could not start managed Tor watchdog";
780 return false;
781 }
782
783 return true;
784}
785
786void BtfStopManagedTor()
787{
788 bool fWasRunning = false;
789 CRITICAL_BLOCK(cs_managedTor)
790 {
791 fWasRunning = g_fManagedTor;
792 if (!fWasRunning)
793 return;
794 g_fManagedTorStopping = true;
795 g_fManagedTor = false;
796 g_managedTorStatus = "stopping";
797 }
798#ifdef _WIN32
799 if (g_managedTorProcess.hProcess)
800 {
801 std::string shutdownErr;
802 bool fGraceful = RequestManagedTorShutdown(shutdownErr);
803 DWORD waitResult = WaitForSingleObject(g_managedTorProcess.hProcess,
804 fGraceful ? 10000 : 1000);
805 if (waitResult != WAIT_OBJECT_0)
806 {
807 TerminateProcess(g_managedTorProcess.hProcess, 0);
808 WaitForSingleObject(g_managedTorProcess.hProcess, 5000);
809 }
810 CloseHandle(g_managedTorProcess.hProcess);
811 CloseHandle(g_managedTorProcess.hThread);
812 memset(&g_managedTorProcess, 0, sizeof(g_managedTorProcess));
813 }
814#else
815 if (g_managedTorPid > 0)
816 {
817 std::string shutdownErr;
818 if (!RequestManagedTorShutdown(shutdownErr))
819 kill(g_managedTorPid, SIGTERM);
820 bool fExited = false;
821 for (int i = 0; i < 100; i++)
822 {
823 int status = 0;
824 pid_t r = waitpid(g_managedTorPid, &status, WNOHANG);
825 if (r == g_managedTorPid)
826 {
827 fExited = true;
828 break;
829 }
830 Sleep(100);
831 }
832 if (!fExited)
833 {
834 kill(g_managedTorPid, SIGKILL);
835 waitpid(g_managedTorPid, NULL, 0);
836 }
837 g_managedTorPid = -1;
838 }
839#endif
840 CRITICAL_BLOCK(cs_managedTor)
841 {
842 g_managedTorStatus = "stopped";
843 g_managedTorOnion.clear();
844 g_managedTorSocksPort = 0;
845 g_managedTorControlPort = 0;
846 g_managedTorP2PPort = 0;
847 }
848}
849
850bool BtfManagedTorEnabled()
851{
852 CRITICAL_BLOCK(cs_managedTor)
853 return g_fManagedTor;
854 return false;
855}
856
857std::string BtfManagedTorStatus()
858{
859 CRITICAL_BLOCK(cs_managedTor)
860 {
861 if (!g_fManagedTor && g_managedTorStatus.empty())
862 return "disabled";
863 return g_managedTorStatus;
864 }
865 return "disabled";
866}