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());
182 auto start = reader.
it;
184 while (
auto line = reader.
ReadLine()) {
189 if (line->size() < 4)
continue;
193 m_message.
code = ToIntegral<int>(line->substr(0, 3)).value_or(0);
195 char separator = (*line)[3];
197 if (separator ==
' ') {
219 if (!
m_sock)
return false;
224 }
catch (
const std::runtime_error& e) {
225 LogWarning(
"tor: Error sending command: %s", e.what());
244 while (ptr <
s.size() &&
s[ptr] !=
' ') {
245 type.push_back(
s[ptr]);
250 return make_pair(type,
s.substr(ptr));
261 std::map<std::string,std::string> mapping;
263 while (ptr <
s.size()) {
264 std::string key, value;
265 while (ptr <
s.size() &&
s[ptr] !=
'=' &&
s[ptr] !=
' ') {
266 key.push_back(
s[ptr]);
270 return std::map<std::string,std::string>();
274 if (ptr <
s.size() &&
s[ptr] ==
'"') {
276 bool escape_next =
false;
277 while (ptr <
s.size() && (escape_next ||
s[ptr] !=
'"')) {
279 escape_next = (
s[ptr] ==
'\\' && !escape_next);
280 value.push_back(
s[ptr]);
284 return std::map<std::string,std::string>();
296 std::string escaped_value;
297 for (
size_t i = 0; i < value.size(); ++i) {
298 if (value[i] ==
'\\') {
304 if (value[i] ==
'n') {
305 escaped_value.push_back(
'\n');
306 }
else if (value[i] ==
't') {
307 escaped_value.push_back(
'\t');
308 }
else if (value[i] ==
'r') {
309 escaped_value.push_back(
'\r');
310 }
else if (
'0' <= value[i] && value[i] <=
'7') {
315 for (j = 1; j < 3 && (i+j) < value.size() &&
'0' <= value[i+j] && value[i+j] <=
'7'; ++j) {}
319 if (j == 3 && value[i] >
'3') {
322 const auto end{i + j};
326 val += value[i++] -
'0';
328 escaped_value.push_back(
char(val));
332 escaped_value.push_back(value[i]);
335 escaped_value.push_back(value[i]);
338 value = escaped_value;
340 while (ptr <
s.size() &&
s[ptr] !=
' ') {
341 value.push_back(
s[ptr]);
345 if (ptr <
s.size() &&
s[ptr] ==
' ')
347 mapping[key] = value;
353 : m_tor_control_center(tor_control_center),
438 std::string socks_location;
440 for (
const auto& line : reply.
lines) {
441 if (line.starts_with(
"net/listeners/socks=")) {
442 const std::string port_list_str = line.substr(20);
443 std::vector<std::string> port_list =
SplitString(port_list_str,
' ');
445 for (
auto& portstr : port_list) {
446 if (portstr.empty())
continue;
447 if ((portstr[0] ==
'"' || portstr[0] ==
'\'') && portstr.size() >= 2 && (*portstr.rbegin() == portstr[0])) {
448 portstr = portstr.substr(1, portstr.size() - 2);
449 if (portstr.empty())
continue;
451 socks_location = portstr;
452 if (portstr.starts_with(
"127.0.0.1:")) {
459 if (!socks_location.empty()) {
462 LogWarning(
"tor: Get SOCKS port command returned nothing");
465 LogWarning(
"tor: Get SOCKS port command failed with unrecognized command (You probably should upgrade Tor)");
467 LogWarning(
"tor: Get SOCKS port command failed; error code %d", reply.
code);
472 if (!socks_location.empty()) {
491 const bool onion_allowed_by_onlynet{
493 std::any_of(onlynets.begin(), onlynets.end(), [](
const auto& n) {
494 return ParseNetwork(n) == NET_ONION;
497 if (onion_allowed_by_onlynet) {
509 return strprintf(
"ADD_ONION %s%s Port=%i,%s",
511 enable_pow ?
" PoWDefensesEnabled=1" :
"",
512 Params().GetDefaultPort(),
519 LogDebug(
BCLog::TOR,
"ADD_ONION successful (PoW defenses %s)", pow_was_enabled ?
"enabled" :
"disabled");
520 for (
const std::string &
s : reply.
lines) {
522 std::map<std::string,std::string>::iterator i;
523 if ((i =
m.find(
"ServiceID")) !=
m.end())
525 if ((i =
m.find(
"PrivateKey")) !=
m.end())
529 LogWarning(
"tor: Error parsing ADD_ONION parameters:");
530 for (
const std::string &
s : reply.
lines) {
545 LogWarning(
"tor: Add onion failed with unrecognized command (You probably need to upgrade Tor)");
550 add_onion_cb(conn, reply, false);
575 add_onion_cb(conn, reply, true);
598static 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)
600 CHMAC_SHA256 computeHash((
const uint8_t*)key.data(), key.size());
602 computeHash.
Write(cookie.data(), cookie.size());
603 computeHash.
Write(client_nonce.data(), client_nonce.size());
604 computeHash.
Write(server_nonce.data(), server_nonce.size());
605 computeHash.
Finalize(computedHash.data());
613 if (reply.
lines.empty()) {
614 LogWarning(
"tor: AUTHCHALLENGE reply was empty");
618 if (l.first ==
"AUTHCHALLENGE") {
624 std::vector<uint8_t> server_hash =
ParseHex(
m[
"SERVERHASH"]);
625 std::vector<uint8_t> server_nonce =
ParseHex(
m[
"SERVERNONCE"]);
627 if (server_nonce.size() != 32) {
628 LogWarning(
"tor: ServerNonce is not 32 bytes, as required by spec");
633 if (computed_server_hash != server_hash) {
634 LogWarning(
"tor: ServerHash %s does not match expected ServerHash %s",
HexStr(server_hash),
HexStr(computed_server_hash));
641 LogWarning(
"tor: Invalid reply to AUTHCHALLENGE");
644 LogWarning(
"tor: SAFECOOKIE authentication challenge failed");
651 std::set<std::string> methods;
652 std::string cookiefile;
658 for (
const std::string &
s : reply.
lines) {
660 if (l.first ==
"AUTH") {
662 std::map<std::string,std::string>::iterator i;
663 if ((i =
m.find(
"METHODS")) !=
m.end()) {
664 std::vector<std::string> m_vec =
SplitString(i->second,
',');
665 methods = std::set<std::string>(m_vec.begin(), m_vec.end());
667 if ((i =
m.find(
"COOKIEFILE")) !=
m.end())
668 cookiefile = i->second;
669 }
else if (l.first ==
"VERSION") {
671 std::map<std::string,std::string>::iterator i;
672 if ((i =
m.find(
"Tor")) !=
m.end()) {
677 for (
const std::string &
s : methods) {
685 std::string torpassword =
gArgs.
GetArg(
"-torpassword",
"");
686 if (!torpassword.empty()) {
687 if (methods.contains(
"HASHEDPASSWORD")) {
692 LogWarning(
"tor: Password provided with -torpassword, but HASHEDPASSWORD authentication is not available");
694 }
else if (methods.contains(
"NULL")) {
697 }
else if (methods.contains(
"SAFECOOKIE")) {
699 LogDebug(
BCLog::TOR,
"Using SAFECOOKIE authentication, reading cookie authentication from %s", cookiefile);
701 if (status_cookie.first && status_cookie.second.size() ==
TOR_COOKIE_SIZE) {
703 m_cookie = std::vector<uint8_t>(status_cookie.second.begin(), status_cookie.second.end());
708 if (status_cookie.first) {
709 LogWarning(
"tor: Authentication cookie %s is not exactly %i bytes, as is required by the spec", cookiefile,
TOR_COOKIE_SIZE);
711 LogWarning(
"tor: Authentication cookie %s could not be opened (check permissions)", cookiefile);
714 }
else if (methods.contains(
"HASHEDPASSWORD")) {
715 LogWarning(
"tor: The only supported authentication mechanism left is password, but no password provided with -torpassword");
717 LogWarning(
"tor: No supported authentication method");
720 LogWarning(
"tor: Requesting protocol info failed");
729 LogWarning(
"tor: Error sending initial protocolinfo command");
752 struct in_addr onion_service_target;
753 onion_service_target.s_addr = htonl(INADDR_LOOPBACK);
754 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 const 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 ERR
Ignored if passed to Wait(), but could be set in the occurred events if an exceptional condition has ...
static constexpr Event RECV
If passed to Wait(), then it will wait for readiness to read from the socket.
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.
std::vector< std::byte > m_recv_buffer
Buffer for incoming data.
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::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
void ThreadControl()
Main control thread.
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.
#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.
#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)
@ 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.
std::span< conststd::byte >::iterator it
std::optional< std::string > ReadLine()
Returns a string from current iterator position up to (but not including) next and advances iterator...
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.