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),
437 std::string socks_location;
439 for (
const auto& line : reply.
lines) {
440 if (line.starts_with(
"net/listeners/socks=")) {
441 const std::string port_list_str = line.substr(20);
442 std::vector<std::string> port_list =
SplitString(port_list_str,
' ');
444 for (
auto& portstr : port_list) {
445 if (portstr.empty())
continue;
446 if ((portstr[0] ==
'"' || portstr[0] ==
'\'') && portstr.size() >= 2 && (*portstr.rbegin() == portstr[0])) {
447 portstr = portstr.substr(1, portstr.size() - 2);
448 if (portstr.empty())
continue;
450 socks_location = portstr;
451 if (portstr.starts_with(
"127.0.0.1:")) {
458 if (!socks_location.empty()) {
461 LogWarning(
"tor: Get SOCKS port command returned nothing");
464 LogWarning(
"tor: Get SOCKS port command failed with unrecognized command (You probably should upgrade Tor)");
466 LogWarning(
"tor: Get SOCKS port command failed; error code %d", reply.
code);
471 if (!socks_location.empty()) {
490 const bool onion_allowed_by_onlynet{
492 std::any_of(onlynets.begin(), onlynets.end(), [](
const auto& n) {
493 return ParseNetwork(n) == NET_ONION;
496 if (onion_allowed_by_onlynet) {
508 return strprintf(
"ADD_ONION %s%s Port=%i,%s",
510 enable_pow ?
" PoWDefensesEnabled=1" :
"",
511 Params().GetDefaultPort(),
518 LogDebug(
BCLog::TOR,
"ADD_ONION successful (PoW defenses %s)", pow_was_enabled ?
"enabled" :
"disabled");
519 for (
const std::string &
s : reply.
lines) {
521 std::map<std::string,std::string>::iterator i;
522 if ((i =
m.find(
"ServiceID")) !=
m.end())
524 if ((i =
m.find(
"PrivateKey")) !=
m.end())
528 LogWarning(
"tor: Error parsing ADD_ONION parameters:");
529 for (
const std::string &
s : reply.
lines) {
544 LogWarning(
"tor: Add onion failed with unrecognized command (You probably need to upgrade Tor)");
549 add_onion_cb(conn, reply, false);
574 add_onion_cb(conn, reply, true);
597static 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)
599 CHMAC_SHA256 computeHash((
const uint8_t*)key.data(), key.size());
601 computeHash.
Write(cookie.data(), cookie.size());
602 computeHash.
Write(client_nonce.data(), client_nonce.size());
603 computeHash.
Write(server_nonce.data(), server_nonce.size());
604 computeHash.
Finalize(computedHash.data());
612 if (reply.
lines.empty()) {
613 LogWarning(
"tor: AUTHCHALLENGE reply was empty");
617 if (l.first ==
"AUTHCHALLENGE") {
623 std::vector<uint8_t> server_hash =
ParseHex(
m[
"SERVERHASH"]);
624 std::vector<uint8_t> server_nonce =
ParseHex(
m[
"SERVERNONCE"]);
626 if (server_nonce.size() != 32) {
627 LogWarning(
"tor: ServerNonce is not 32 bytes, as required by spec");
632 if (computed_server_hash != server_hash) {
633 LogWarning(
"tor: ServerHash %s does not match expected ServerHash %s",
HexStr(server_hash),
HexStr(computed_server_hash));
640 LogWarning(
"tor: Invalid reply to AUTHCHALLENGE");
643 LogWarning(
"tor: SAFECOOKIE authentication challenge failed");
650 std::set<std::string> methods;
651 std::string cookiefile;
657 for (
const std::string &
s : reply.
lines) {
659 if (l.first ==
"AUTH") {
661 std::map<std::string,std::string>::iterator i;
662 if ((i =
m.find(
"METHODS")) !=
m.end()) {
663 std::vector<std::string> m_vec =
SplitString(i->second,
',');
664 methods = std::set<std::string>(m_vec.begin(), m_vec.end());
666 if ((i =
m.find(
"COOKIEFILE")) !=
m.end())
667 cookiefile = i->second;
668 }
else if (l.first ==
"VERSION") {
670 std::map<std::string,std::string>::iterator i;
671 if ((i =
m.find(
"Tor")) !=
m.end()) {
676 for (
const std::string &
s : methods) {
684 std::string torpassword =
gArgs.
GetArg(
"-torpassword",
"");
685 if (!torpassword.empty()) {
686 if (methods.contains(
"HASHEDPASSWORD")) {
691 LogWarning(
"tor: Password provided with -torpassword, but HASHEDPASSWORD authentication is not available");
693 }
else if (methods.contains(
"NULL")) {
696 }
else if (methods.contains(
"SAFECOOKIE")) {
698 LogDebug(
BCLog::TOR,
"Using SAFECOOKIE authentication, reading cookie authentication from %s", cookiefile);
700 if (status_cookie.first && status_cookie.second.size() ==
TOR_COOKIE_SIZE) {
702 m_cookie = std::vector<uint8_t>(status_cookie.second.begin(), status_cookie.second.end());
707 if (status_cookie.first) {
708 LogWarning(
"tor: Authentication cookie %s is not exactly %i bytes, as is required by the spec", cookiefile,
TOR_COOKIE_SIZE);
710 LogWarning(
"tor: Authentication cookie %s could not be opened (check permissions)", cookiefile);
713 }
else if (methods.contains(
"HASHEDPASSWORD")) {
714 LogWarning(
"tor: The only supported authentication mechanism left is password, but no password provided with -torpassword");
716 LogWarning(
"tor: No supported authentication method");
719 LogWarning(
"tor: Requesting protocol info failed");
728 LogWarning(
"tor: Error sending initial protocolinfo command");
751 struct in_addr onion_service_target;
752 onion_service_target.s_addr = htonl(INADDR_LOOPBACK);
753 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.
std::string ToString(const T &t)
Locale-independent version of std::to_string.
void ReplaceAll(std::string &in_out, const std::string &search, const std::string &substitute)
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.