52static const std::string
TOR_SAFE_SERVERKEY =
"Tor safe cookie authentication server-to-controller hash";
54static const std::string
TOR_SAFE_CLIENTKEY =
"Tor safe cookie authentication controller-to-server hash";
77 : m_interrupt(interrupt)
93 if (!control_service.has_value()) {
94 LogWarning(
"tor: Failed to look up control center %s", tor_control_center);
100 LogWarning(
"tor: Error connecting to address %s", tor_control_center);
122 if (!
m_sock)
return false;
124 const bool connected{
m_sock->IsConnected(errmsg)};
125 if (!connected && !errmsg.empty()) {
133 if (!
m_sock)
return false;
150 if (!
m_sock)
return false;
173 }
catch (
const std::runtime_error& e) {
174 LogWarning(
"tor: Error processing receive buffer: %s", e.what());
188 if (line->size() < 4)
continue;
192 m_message.
code = ToIntegral<int>(line->substr(0, 3)).value_or(0);
194 char separator = (*line)[3];
196 if (separator ==
' ') {
218 if (!
m_sock)
return false;
223 }
catch (
const std::runtime_error& e) {
224 LogWarning(
"tor: Error sending command: %s", e.what());
243 while (ptr <
s.size() &&
s[ptr] !=
' ') {
244 type.push_back(
s[ptr]);
249 return make_pair(type,
s.substr(ptr));
260 std::map<std::string,std::string> mapping;
262 while (ptr <
s.size()) {
263 std::string key, value;
264 while (ptr <
s.size() &&
s[ptr] !=
'=' &&
s[ptr] !=
' ') {
265 key.push_back(
s[ptr]);
269 return std::map<std::string,std::string>();
273 if (ptr <
s.size() &&
s[ptr] ==
'"') {
275 bool escape_next =
false;
276 while (ptr <
s.size() && (escape_next ||
s[ptr] !=
'"')) {
278 escape_next = (
s[ptr] ==
'\\' && !escape_next);
279 value.push_back(
s[ptr]);
283 return std::map<std::string,std::string>();
295 std::string escaped_value;
296 for (
size_t i = 0; i < value.size(); ++i) {
297 if (value[i] ==
'\\') {
303 if (value[i] ==
'n') {
304 escaped_value.push_back(
'\n');
305 }
else if (value[i] ==
't') {
306 escaped_value.push_back(
'\t');
307 }
else if (value[i] ==
'r') {
308 escaped_value.push_back(
'\r');
309 }
else if (
'0' <= value[i] && value[i] <=
'7') {
314 for (j = 1; j < 3 && (i+j) < value.size() &&
'0' <= value[i+j] && value[i+j] <=
'7'; ++j) {}
318 if (j == 3 && value[i] >
'3') {
321 const auto end{i + j};
325 val += value[i++] -
'0';
327 escaped_value.push_back(
char(val));
331 escaped_value.push_back(value[i]);
334 escaped_value.push_back(value[i]);
337 value = escaped_value;
339 while (ptr <
s.size() &&
s[ptr] !=
' ') {
340 value.push_back(
s[ptr]);
344 if (ptr <
s.size() &&
s[ptr] ==
' ')
346 mapping[key] = value;
352 : m_tor_control_center(tor_control_center),
416 std::string socks_location;
418 for (
const auto& line : reply.
lines) {
419 if (line.starts_with(
"net/listeners/socks=")) {
420 const std::string port_list_str = line.substr(20);
421 std::vector<std::string> port_list =
SplitString(port_list_str,
' ');
423 for (
auto& portstr : port_list) {
424 if (portstr.empty())
continue;
425 if ((portstr[0] ==
'"' || portstr[0] ==
'\'') && portstr.size() >= 2 && (*portstr.rbegin() == portstr[0])) {
426 portstr = portstr.substr(1, portstr.size() - 2);
427 if (portstr.empty())
continue;
429 socks_location = portstr;
430 if (portstr.starts_with(
"127.0.0.1:")) {
437 if (!socks_location.empty()) {
440 LogWarning(
"tor: Get SOCKS port command returned nothing");
443 LogWarning(
"tor: Get SOCKS port command failed with unrecognized command (You probably should upgrade Tor)");
445 LogWarning(
"tor: Get SOCKS port command failed; error code %d", reply.
code);
450 if (!socks_location.empty()) {
469 const bool onion_allowed_by_onlynet{
471 std::any_of(onlynets.begin(), onlynets.end(), [](
const auto& n) {
472 return ParseNetwork(n) == NET_ONION;
475 if (onion_allowed_by_onlynet) {
487 return strprintf(
"ADD_ONION %s%s Port=%i,%s",
489 enable_pow ?
" PoWDefensesEnabled=1" :
"",
490 Params().GetDefaultPort(),
497 LogDebug(
BCLog::TOR,
"ADD_ONION successful (PoW defenses %s)", pow_was_enabled ?
"enabled" :
"disabled");
498 for (
const std::string &
s : reply.
lines) {
500 std::map<std::string,std::string>::iterator i;
501 if ((i =
m.find(
"ServiceID")) !=
m.end())
503 if ((i =
m.find(
"PrivateKey")) !=
m.end())
507 LogWarning(
"tor: Error parsing ADD_ONION parameters:");
508 for (
const std::string &
s : reply.
lines) {
523 LogWarning(
"tor: Add onion failed with unrecognized command (You probably need to upgrade Tor)");
528 add_onion_cb(conn, reply, false);
553 add_onion_cb(conn, reply, true);
576static std::vector<uint8_t>
ComputeResponse(std::string_view key, std::span<const uint8_t> cookie, std::span<const uint8_t> client_nonce, std::span<const uint8_t> server_nonce)
578 CHMAC_SHA256 computeHash((
const uint8_t*)key.data(), key.size());
580 computeHash.
Write(cookie.data(), cookie.size());
581 computeHash.
Write(client_nonce.data(), client_nonce.size());
582 computeHash.
Write(server_nonce.data(), server_nonce.size());
583 computeHash.
Finalize(computedHash.data());
591 if (reply.
lines.empty()) {
592 LogWarning(
"tor: AUTHCHALLENGE reply was empty");
596 if (l.first ==
"AUTHCHALLENGE") {
602 std::vector<uint8_t> server_hash =
ParseHex(
m[
"SERVERHASH"]);
603 std::vector<uint8_t> server_nonce =
ParseHex(
m[
"SERVERNONCE"]);
605 if (server_nonce.size() != 32) {
606 LogWarning(
"tor: ServerNonce is not 32 bytes, as required by spec");
611 if (computed_server_hash != server_hash) {
612 LogWarning(
"tor: ServerHash %s does not match expected ServerHash %s",
HexStr(server_hash),
HexStr(computed_server_hash));
619 LogWarning(
"tor: Invalid reply to AUTHCHALLENGE");
622 LogWarning(
"tor: SAFECOOKIE authentication challenge failed");
629 std::set<std::string> methods;
630 std::string cookiefile;
636 for (
const std::string &
s : reply.
lines) {
638 if (l.first ==
"AUTH") {
640 std::map<std::string,std::string>::iterator i;
641 if ((i =
m.find(
"METHODS")) !=
m.end()) {
642 std::vector<std::string> m_vec =
SplitString(i->second,
',');
643 methods = std::set<std::string>(m_vec.begin(), m_vec.end());
645 if ((i =
m.find(
"COOKIEFILE")) !=
m.end())
646 cookiefile = i->second;
647 }
else if (l.first ==
"VERSION") {
649 std::map<std::string,std::string>::iterator i;
650 if ((i =
m.find(
"Tor")) !=
m.end()) {
655 for (
const std::string &
s : methods) {
663 std::string torpassword =
gArgs.
GetArg(
"-torpassword",
"");
664 if (!torpassword.empty()) {
665 if (methods.contains(
"HASHEDPASSWORD")) {
670 LogWarning(
"tor: Password provided with -torpassword, but HASHEDPASSWORD authentication is not available");
672 }
else if (methods.contains(
"NULL")) {
675 }
else if (methods.contains(
"SAFECOOKIE")) {
677 LogDebug(
BCLog::TOR,
"Using SAFECOOKIE authentication, reading cookie authentication from %s", cookiefile);
679 if (status_cookie.first && status_cookie.second.size() ==
TOR_COOKIE_SIZE) {
681 m_cookie = std::vector<uint8_t>(status_cookie.second.begin(), status_cookie.second.end());
686 if (status_cookie.first) {
687 LogWarning(
"tor: Authentication cookie %s is not exactly %i bytes, as is required by the spec", cookiefile,
TOR_COOKIE_SIZE);
689 LogWarning(
"tor: Authentication cookie %s could not be opened (check permissions)", cookiefile);
692 }
else if (methods.contains(
"HASHEDPASSWORD")) {
693 LogWarning(
"tor: The only supported authentication mechanism left is password, but no password provided with -torpassword");
695 LogWarning(
"tor: No supported authentication method");
698 LogWarning(
"tor: Requesting protocol info failed");
707 LogWarning(
"tor: Error sending initial protocolinfo command");
734 struct in_addr onion_service_target;
735 onion_service_target.s_addr = htonl(INADDR_LOOPBACK);
736 return {onion_service_target, port};
const CChainParams & Params()
Return the currently selected parameters.
#define Assume(val)
Assume is the identity function.
std::vector< std::string > GetArgs(const std::string &strArg) const EXCLUSIVE_LOCKS_REQUIRED(!cs_args)
Return a vector of strings of the given argument.
std::string GetArg(const std::string &strArg, const std::string &strDefault) const EXCLUSIVE_LOCKS_REQUIRED(!cs_args)
Return string argument or default value.
fs::path GetDataDirNet() const EXCLUSIVE_LOCKS_REQUIRED(!cs_args)
Get data directory path with appended network identifier.
A hasher class for HMAC-SHA-256.
CHMAC_SHA256 & Write(const unsigned char *data, size_t len)
void Finalize(unsigned char hash[OUTPUT_SIZE])
static constexpr size_t OUTPUT_SIZE
A combination of a network address (CNetAddr) and a (TCP) port.
std::string ToStringAddrPort() const
A helper class for interruptible sleeps.
virtual bool sleep_for(Clock::duration rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut)
Sleep for the given duration.
void Add(Network net) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
static constexpr Event RecvEvent
If passed to Wait(), then it will wait for readiness to read from the socket.
static constexpr Event ErrorEvent
Ignored if passed to Wait(), but could be set in the occurred events if an exceptional condition has ...
Low-level handling for Tor control connection.
TorControlReply m_message
Message being received.
std::deque< ReplyHandlerCB > m_reply_handlers
Response handlers.
bool Command(const std::string &cmd, const ReplyHandlerCB &reply_handler)
Send a command, register a handler for the reply.
CThreadInterrupt & m_interrupt
Reference to interrupt object for clean shutdown.
bool WaitForData(std::chrono::milliseconds timeout)
Wait for data to be available on the socket.
std::string m_recv_buffer
Buffer for incoming data.
std::function< void(TorControlConnection &, const TorControlReply &)> ReplyHandlerCB
bool ProcessBuffer()
Process complete lines from the receive buffer.
bool ReceiveAndProcess()
Read available data from socket and process complete replies.
void Disconnect()
Disconnect from Tor control port.
TorControlConnection(CThreadInterrupt &interrupt)
Create a new TorControlConnection.
bool Connect(const std::string &tor_control_center)
Connect to a Tor control port.
std::unique_ptr< Sock > m_sock
Socket for the connection.
bool IsConnected() const
Check if the connection is established.
Reply from Tor, can be single or multi-line.
std::vector< std::string > lines
CThreadInterrupt m_interrupt
fs::path GetPrivateKeyFile()
Get name of file to store private key in.
void connected_cb(TorControlConnection &conn)
Callback after successful connection.
void get_socks_cb(TorControlConnection &conn, const TorControlReply &reply)
Callback for GETINFO net/listeners/socks result.
void add_onion_cb(TorControlConnection &conn, const TorControlReply &reply, bool pow_was_enabled)
Callback for ADD_ONION result.
const std::string m_tor_control_center
std::vector< uint8_t > m_client_nonce
ClientNonce for SAFECOOKIE auth.
void disconnected_cb(TorControlConnection &conn)
Callback after connection lost or failed connection attempt.
void authchallenge_cb(TorControlConnection &conn, const TorControlReply &reply)
Callback for AUTHCHALLENGE result.
TorControlConnection m_conn
std::atomic< bool > m_reconnect
void Interrupt()
Interrupt the controller thread.
std::string m_private_key
void Join()
Wait for the controller thread to exit.
void auth_cb(TorControlConnection &conn, const TorControlReply &reply)
Callback for AUTHENTICATE result.
void protocolinfo_cb(TorControlConnection &conn, const TorControlReply &reply)
Callback for PROTOCOLINFO result.
std::chrono::duration< double > m_reconnect_timeout
std::vector< uint8_t > m_cookie
Cookie for SAFECOOKIE auth.
size_t Consumed() const
Returns number of bytes already read from buffer.
std::optional< std::string_view > ReadLine() LIFETIMEBOUND
Returns a string from current iterator position up to (but not including) next and advances iterator...
#define WSAGetLastError()
static std::string PathToString(const path &path)
Convert path object to a byte string.
static path PathFromString(const std::string &string)
Convert byte string to path object.
std::string HexStr(const std::span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
std::thread thread
Thread variable should be after other struct members so the thread does not start until the other mem...
#define LogDebug(category,...)
std::vector< std::string > SplitString(std::string_view str, char sep)
void TraceThread(std::string_view thread_name, std::function< void()> thread_func)
A wrapper for do-something-once thread functions.
void ReplaceAll(std::string &in_out, std::string_view search, std::string_view substitute)
Replace every non-overlapping occurrence of search with substitute, treating both literally; the repl...
std::string ToString(const T &t)
Locale-independent version of std::to_string.
void RemoveLocal(const CService &addr)
bool AddLocal(const CService &addr_, int nScore, bool add_even_if_unreachable)
@ NET_ONION
TOR (v2 or v3)
std::unique_ptr< Sock > ConnectDirectly(const CService &dest, bool manual_connection)
Create a socket and try to connect to the specified service.
bool SetProxy(enum Network net, const Proxy &addrProxy)
std::vector< CService > Lookup(const std::string &name, uint16_t portDefault, bool fAllowLookup, unsigned int nMaxSolutions, DNSLookupFn dns_lookup_function)
Resolve a service string to its corresponding service.
ReachableNets g_reachable_nets
CService LookupNumeric(const std::string &name, uint16_t portDefault, DNSLookupFn dns_lookup_function)
Resolve a service string with a numeric IP to its first corresponding service.
void GetRandBytes(std::span< unsigned char > bytes) noexcept
Generate random data via the internal PRNG.
bool WriteBinaryFile(const fs::path &filename, const std::string &data)
Write contents of std::string to a file.
std::pair< bool, std::string > ReadBinaryFile(const fs::path &filename, size_t maxsize)
Read full contents of a file and return them in a std::string.
std::string NetworkErrorString(int err)
Return readable error string for a network error code.
std::vector< Byte > ParseHex(std::string_view hex_str)
Like TryParseHex, but returns an empty vector on invalid input.
static const std::string TOR_SAFE_CLIENTKEY
For computing clientHash in SAFECOOKIE.
constexpr std::chrono::duration< double > RECONNECT_TIMEOUT_MAX
Maximum reconnect timeout in seconds to prevent excessive delays.
constexpr int TOR_COOKIE_SIZE
Tor cookie size (from control-spec.txt)
constexpr int TOR_NONCE_SIZE
Size of client/server nonce for SAFECOOKIE.
constexpr std::chrono::duration< double > RECONNECT_TIMEOUT_START
Exponential backoff configuration - initial timeout in seconds.
static std::string MakeAddOnionCmd(const std::string &private_key, const std::string &target, bool enable_pow)
const std::string DEFAULT_TOR_CONTROL
Default control ip and port.
std::pair< std::string, std::string > SplitTorReplyLine(const std::string &s)
static const std::string TOR_SAFE_SERVERKEY
For computing server_hash in SAFECOOKIE.
static std::vector< uint8_t > ComputeResponse(std::string_view key, std::span< const uint8_t > cookie, std::span< const uint8_t > client_nonce, std::span< const uint8_t > server_nonce)
Compute Tor SAFECOOKIE response.
constexpr int MAX_LINE_COUNT
Maximum number of lines received on TorControlConnection per reply to avoid memory exhaustion.
constexpr double RECONNECT_TIMEOUT_EXP
Exponential backoff configuration - growth factor.
std::map< std::string, std::string > ParseTorReplyMapping(const std::string &s)
Parse reply arguments in the form 'METHODS=COOKIE,SAFECOOKIE COOKIEFILE=".../control_auth_cookie"'.
CService DefaultOnionServiceTarget(uint16_t port)
constexpr auto SOCKET_SEND_TIMEOUT
Timeout for socket operations.
constexpr int MAX_LINE_LENGTH
Maximum length for lines received on TorControlConnection.
constexpr int TOR_REPLY_SYNTAX_ERROR
Syntax error in command argument.
constexpr uint16_t DEFAULT_TOR_SOCKS_PORT
Functionality for communicating with Tor.
constexpr int TOR_REPLY_OK
Tor control reply code.
constexpr int TOR_REPLY_UNRECOGNIZED
constexpr int DEFAULT_TOR_CONTROL_PORT
std::string SanitizeString(std::string_view str, int rule)
Remove unsafe chars.