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-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
commitceae3d076b5ad11ee84729ae43f14e9312896646 (patch)
tree7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/tor.cpp
parentbe63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff)
parent66e0b9158d050c46b1ff2509faeee3f4573cac1c (diff)
Bring main onto the line this project actually ships
main had drifted to a history with no ancestor in common with the tree we build and deploy. It sat at be63e83 (2026-08-09) with 28 commits; the shipped line, deploy/soak-2026-08-21, had 383. The two shared nothing -- git merge-base returns no result at all -- so anyone cloning this repository landed on a default branch that was neither current nor related to the running node. The 28 commits on main were audited before this merge: every one is already present in the tree by content, including the two that are hardest to see because they were rewritten afterwards -- the payout reorg-safety work (PayoutBlockStillOnChain, since evolved to re-check on every retry rather than once at maturity) and the nostr relay certificate verification (SSL_set1_host, now with a Windows root-store path). Nothing on main is lost by this merge. Recorded as a merge rather than a force-push: main only ever moves forward, its old history stays reachable as the first parent, and this is revertible. The tree is taken wholesale from the merged branch, so main's content is now exactly what was built and tested. What this brings onto main: - compactblock: BIP152-style compact block relay ported from upstream 1.2.23, primitives and unit test only, with the RAND_bytes failure path closed and the reconstruction allocation bounded. - upstream/main 17741d9: isolated testnet parameters and genesis, optional 2 MB large pages for RandomX. Mainnet genesis verified byte-identical through upstream's BuildGenesisBlock() refactor. - deploy: every image built on bookworm so binaries run on Debian 12, and the container build now honours the RandomX and libsecp256k1 pins it had been silently ignoring since before 1.2.15. Gates at this tree: 15/15 selftests, compactblock's 17 checks, consensus constants unchanged against e4f898c apart from the pre-existing MAX_BLOCK_SIZE_GEN, mainnet genesis intact, money-log check ok.
Diffstat (limited to 'src/tor.cpp')
-rw-r--r--src/tor.cpp892
1 files changed, 892 insertions, 0 deletions
diff --git a/src/tor.cpp b/src/tor.cpp
new file mode 100644
index 0000000..fea6912
--- /dev/null
+++ b/src/tor.cpp
@@ -0,0 +1,892 @@
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
503bool BtfBundledTorPath(std::string& torPathOut)
504{
505 torPathOut.clear();
506 std::string exeDir = ExecutableDir();
507#ifdef _WIN32
508 std::string path = PathJoin(exeDir, "tor/tor.exe");
509#else
510 std::string path = PathJoin(exeDir, "tor/tor");
511#endif
512 if (!FileIsExecutableCandidate(path))
513 return false;
514 torPathOut = path;
515 return true;
516}
517
518std::string BtfBuildManagedTorrcForTest(const std::string& dataDir,
519 const std::string& hiddenServiceDir,
520 unsigned short socksPort,
521 unsigned short controlPort,
522 unsigned short p2pPort)
523{
524 std::string torData = NormalizeTorrcPath(dataDir);
525 std::string hsDir = NormalizeTorrcPath(hiddenServiceDir);
526 std::string s;
527 s += "# Generated by Bitflash. Edit only while Bitflash is not managing Tor.\n";
528 s += "DataDirectory " + QuoteTorrcPath(torData) + "\n";
529 s += strprintf("SocksPort 127.0.0.1:%u IsolateSOCKSAuth IsolateClientAddr IsolateDestAddr IsolateDestPort\n",
530 (unsigned)socksPort);
531 s += strprintf("ControlPort 127.0.0.1:%u\n", (unsigned)controlPort);
532 s += "CookieAuthentication 1\n";
533 s += "HiddenServiceDir " + QuoteTorrcPath(hsDir) + "\n";
534 s += "HiddenServiceVersion 3\n";
535 s += strprintf("HiddenServicePort %u 127.0.0.1:%u\n",
536 (unsigned)p2pPort, (unsigned)p2pPort);
537 return s;
538}
539
540static bool StartTorProcess(const std::string& torPath, const std::string& torrcPath,
541 std::string& errOut)
542{
543#ifdef _WIN32
544 memset(&g_managedTorProcess, 0, sizeof(g_managedTorProcess));
545 STARTUPINFOA si;
546 memset(&si, 0, sizeof(si));
547 si.cb = sizeof(si);
548 std::string cmd = QuoteCommandArg(torPath) + " -f " + QuoteCommandArg(torrcPath);
549 std::vector<char> mutableCmd(cmd.begin(), cmd.end());
550 mutableCmd.push_back(0);
551 if (!CreateProcessA(NULL, &mutableCmd[0], NULL, NULL, FALSE, CREATE_NO_WINDOW,
552 NULL, NULL, &si, &g_managedTorProcess))
553 {
554 errOut = strprintf("could not start Tor process %s (error %lu)",
555 torPath.c_str(), (unsigned long)GetLastError());
556 return false;
557 }
558 return true;
559#else
560 g_managedTorPid = fork();
561 if (g_managedTorPid < 0)
562 {
563 errOut = strprintf("could not fork Tor process: %s", strerror(errno));
564 return false;
565 }
566 if (g_managedTorPid == 0)
567 {
568 execlp(torPath.c_str(), torPath.c_str(), "-f", torrcPath.c_str(), (char*)NULL);
569 _exit(127);
570 }
571 return true;
572#endif
573}
574
575static bool ManagedTorProcessAlive(bool& fExited, int& nExitCode)
576{
577 fExited = false;
578 nExitCode = 0;
579#ifdef _WIN32
580 HANDLE hProcess = NULL;
581 CRITICAL_BLOCK(cs_managedTor)
582 hProcess = g_managedTorProcess.hProcess;
583 if (!hProcess)
584 return false;
585 DWORD code = 0;
586 if (!GetExitCodeProcess(hProcess, &code))
587 return false;
588 if (code != STILL_ACTIVE)
589 {
590 fExited = true;
591 nExitCode = (int)code;
592 return false;
593 }
594 return true;
595#else
596 pid_t pid = -1;
597 CRITICAL_BLOCK(cs_managedTor)
598 pid = g_managedTorPid;
599 if (pid <= 0)
600 return false;
601 int status = 0;
602 pid_t r = waitpid(pid, &status, WNOHANG);
603 if (r == 0)
604 return true;
605 if (r == pid)
606 {
607 fExited = true;
608 if (WIFEXITED(status))
609 nExitCode = WEXITSTATUS(status);
610 else if (WIFSIGNALED(status))
611 nExitCode = 128 + WTERMSIG(status);
612 CRITICAL_BLOCK(cs_managedTor)
613 if (g_managedTorPid == pid)
614 g_managedTorPid = -1;
615 }
616 return false;
617#endif
618}
619
620static bool ValidateManagedTorStartup(std::string& errOut)
621{
622 Sleep(500);
623 bool fExited = false;
624 int nExitCode = 0;
625 if (ManagedTorProcessAlive(fExited, nExitCode))
626 return true;
627 if (fExited)
628 errOut = strprintf("Tor exited during startup with code %d", nExitCode);
629 else
630 errOut = "Tor process is not running after startup";
631 return false;
632}
633
634static void MarkManagedTorDead(int nExitCode)
635{
636 BtfStopManagedTor();
637 CRITICAL_BLOCK(cs_managedTor)
638 g_managedTorStatus = strprintf("stopped unexpectedly, Tor exit code %d", nExitCode);
639}
640
641static void ThreadManagedTorWatchdog(void*)
642{
643 for (;;)
644 {
645 Sleep(5000);
646 CRITICAL_BLOCK(cs_managedTor)
647 {
648 if (!g_fManagedTor || g_fManagedTorStopping || fShutdown)
649 return;
650 }
651
652 bool fExited = false;
653 int nExitCode = 0;
654 if (!ManagedTorProcessAlive(fExited, nExitCode))
655 {
656 if (fExited)
657 MarkManagedTorDead(nExitCode);
658 else
659 {
660 BtfStopManagedTor();
661 CRITICAL_BLOCK(cs_managedTor)
662 g_managedTorStatus = "stopped unexpectedly, Tor process handle is gone";
663 }
664 return;
665 }
666 }
667}
668
669static void ThreadManagedTorHostname(void*)
670{
671 std::string hostnamePath;
672 unsigned short p2pPort = 0;
673 CRITICAL_BLOCK(cs_managedTor)
674 {
675 hostnamePath = PathJoin(g_managedTorHiddenServiceDir, "hostname");
676 p2pPort = g_managedTorP2PPort;
677 }
678
679 for (int i = 0; i < 90 && !fShutdown; i++)
680 {
681 std::string onion;
682 if (ReadOneLine(hostnamePath, onion))
683 {
684 std::string endpoint = strprintf("%s:%u", onion.c_str(), (unsigned)p2pPort);
685 std::string err;
686 if (BtfSetLocalOnionEndpoint(endpoint, err))
687 {
688 CRITICAL_BLOCK(cs_managedTor)
689 {
690 g_managedTorOnion = BtfLocalOnionEndpoint();
691 g_managedTorStatus = strprintf("running, SOCKS5 127.0.0.1:%u, onion %s",
692 (unsigned)g_managedTorSocksPort,
693 g_managedTorOnion.c_str());
694 }
695 printf("Managed Tor hidden service ready: %s\n", BtfLocalOnionEndpoint().c_str());
696 return;
697 }
698 CRITICAL_BLOCK(cs_managedTor)
699 g_managedTorStatus = strprintf("running, but generated onion endpoint was rejected: %s",
700 err.c_str());
701 return;
702 }
703 Sleep(1000);
704 }
705
706 BtfStopManagedTor();
707 CRITICAL_BLOCK(cs_managedTor)
708 g_managedTorStatus = "failed: Tor did not create a hidden service hostname within 90s";
709}
710
711bool BtfStartManagedTor(const std::string& torPathOpt, std::string& errOut)
712{
713 CRITICAL_BLOCK(cs_managedTor)
714 {
715 if (g_fManagedTor)
716 {
717 errOut = "managed Tor is already running";
718 return false;
719 }
720 }
721
722 std::string root = PathJoin(GetAppDir(), "managed-tor");
723 std::string dataDir = PathJoin(root, "data");
724 std::string hsDir = PathJoin(root, "onion-service");
725 std::string torrcPath = PathJoin(root, "torrc");
726 if (!EnsureDir(root, errOut) || !EnsureDir(dataDir, errOut) || !EnsureDir(hsDir, errOut))
727 return false;
728
729 unsigned short socksPort = 0;
730 if (!PickLocalPort(socksPort, errOut))
731 return false;
732 unsigned short controlPort = 0;
733 if (!PickLocalPort(controlPort, errOut))
734 return false;
735
736 unsigned short p2pPort = ntohs(nListenPort);
737 std::string torrc = BtfBuildManagedTorrcForTest(dataDir, hsDir, socksPort,
738 controlPort, p2pPort);
739 if (!WriteTextFile(torrcPath, torrc, errOut))
740 return false;
741
742 std::string torPath = ResolveTorPath(torPathOpt);
743 {
744 CRITICAL_BLOCK(cs_managedTor)
745 {
746 g_fManagedTor = true;
747 g_fManagedTorStopping = false;
748 g_managedTorPath = torPath;
749 g_managedTorDataDir = dataDir;
750 g_managedTorHiddenServiceDir = hsDir;
751 g_managedTorSocksPort = socksPort;
752 g_managedTorControlPort = controlPort;
753 g_managedTorP2PPort = p2pPort;
754 g_managedTorOnion.clear();
755 g_managedTorStatus = strprintf("starting %s, SOCKS5 127.0.0.1:%u",
756 torPath.c_str(), (unsigned)socksPort);
757 }
758 }
759
760 if (!StartTorProcess(torPath, torrcPath, errOut))
761 {
762 CRITICAL_BLOCK(cs_managedTor)
763 {
764 g_fManagedTor = false;
765 g_managedTorStatus = strprintf("failed: %s", errOut.c_str());
766 }
767 return false;
768 }
769 if (!ValidateManagedTorStartup(errOut))
770 {
771 BtfStopManagedTor();
772 CRITICAL_BLOCK(cs_managedTor)
773 g_managedTorStatus = strprintf("failed: %s", errOut.c_str());
774 return false;
775 }
776
777 std::string proxyErr;
778 if (!BtfEnableTorProxy(strprintf("127.0.0.1:%u", (unsigned)socksPort), proxyErr))
779 {
780 BtfStopManagedTor();
781 errOut = strprintf("could not enable managed Tor SOCKS5 proxy: %s", proxyErr.c_str());
782 return false;
783 }
784
785 if (_beginthread(ThreadManagedTorHostname, 0, NULL) == (uintptr_t)-1)
786 {
787 BtfStopManagedTor();
788 errOut = "could not start managed Tor hostname watcher";
789 return false;
790 }
791 if (_beginthread(ThreadManagedTorWatchdog, 0, NULL) == (uintptr_t)-1)
792 {
793 BtfStopManagedTor();
794 errOut = "could not start managed Tor watchdog";
795 return false;
796 }
797
798 return true;
799}
800
801void BtfStopManagedTor()
802{
803 bool fWasRunning = false;
804 CRITICAL_BLOCK(cs_managedTor)
805 {
806 fWasRunning = g_fManagedTor;
807 if (!fWasRunning)
808 return;
809 g_fManagedTorStopping = true;
810 g_fManagedTor = false;
811 g_managedTorStatus = "stopping";
812 }
813#ifdef _WIN32
814 if (g_managedTorProcess.hProcess)
815 {
816 std::string shutdownErr;
817 bool fGraceful = RequestManagedTorShutdown(shutdownErr);
818 DWORD waitResult = WaitForSingleObject(g_managedTorProcess.hProcess,
819 fGraceful ? 10000 : 1000);
820 if (waitResult != WAIT_OBJECT_0)
821 {
822 TerminateProcess(g_managedTorProcess.hProcess, 0);
823 WaitForSingleObject(g_managedTorProcess.hProcess, 5000);
824 }
825 CloseHandle(g_managedTorProcess.hProcess);
826 CloseHandle(g_managedTorProcess.hThread);
827 memset(&g_managedTorProcess, 0, sizeof(g_managedTorProcess));
828 }
829#else
830 // Snapshot the pid under the lock. The watchdog's ManagedTorProcessAlive
831 // reaps the same child concurrently and resets g_managedTorPid under
832 // cs_managedTor; reading the global unlocked here could leave us waiting
833 // on ECHILD and then SIGKILLing a recycled PID.
834 pid_t pid = -1;
835 CRITICAL_BLOCK(cs_managedTor)
836 pid = g_managedTorPid;
837 if (pid > 0)
838 {
839 std::string shutdownErr;
840 if (!RequestManagedTorShutdown(shutdownErr))
841 kill(pid, SIGTERM);
842 bool fExited = false;
843 for (int i = 0; i < 100; i++)
844 {
845 int status = 0;
846 pid_t r = waitpid(pid, &status, WNOHANG);
847 // ECHILD means the watchdog already reaped it -- that is exit,
848 // not a reason to signal anything.
849 if (r == pid || (r == -1 && errno == ECHILD))
850 {
851 fExited = true;
852 break;
853 }
854 Sleep(100);
855 }
856 if (!fExited)
857 {
858 kill(pid, SIGKILL);
859 waitpid(pid, NULL, 0);
860 }
861 CRITICAL_BLOCK(cs_managedTor)
862 if (g_managedTorPid == pid)
863 g_managedTorPid = -1;
864 }
865#endif
866 CRITICAL_BLOCK(cs_managedTor)
867 {
868 g_managedTorStatus = "stopped";
869 g_managedTorOnion.clear();
870 g_managedTorSocksPort = 0;
871 g_managedTorControlPort = 0;
872 g_managedTorP2PPort = 0;
873 }
874}
875
876bool BtfManagedTorEnabled()
877{
878 CRITICAL_BLOCK(cs_managedTor)
879 return g_fManagedTor;
880 return false;
881}
882
883std::string BtfManagedTorStatus()
884{
885 CRITICAL_BLOCK(cs_managedTor)
886 {
887 if (!g_fManagedTor && g_managedTorStatus.empty())
888 return "disabled";
889 return g_managedTorStatus;
890 }
891 return "disabled";
892}