23#include <boost/signals2/signal.hpp>
25#include <event2/buffer.h>
26#include <event2/bufferevent.h>
27#include <event2/event.h>
28#include <event2/thread.h>
29#include <event2/util.h>
48 "Tor safe cookie authentication server-to-controller hash";
51 "Tor safe cookie authentication controller-to-server hash";
103 bool Connect(
const std::string &tor_control_center,
139 static void readcb(
struct bufferevent *bev,
void *
ctx);
140 static void eventcb(
struct bufferevent *bev,
short what,
void *
ctx);
154 struct evbuffer *input = bufferevent_get_input(bev);
155 size_t n_read_out = 0;
159 while ((line = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF)) !=
161 std::string s(line, n_read_out);
168 self->
message.
code = LocaleIndependentAtoi<int>(s.substr(0, 3));
185 "tor: Received unexpected sync reply %i\n",
197 LogPrintf(
"tor: Disconnecting because MAX_LINE_LENGTH exceeded\n");
205 if (what & BEV_EVENT_CONNECTED) {
208 }
else if (what & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
209 if (what & BEV_EVENT_ERROR) {
211 "tor: Error connecting to Tor control socket\n");
227 struct sockaddr_storage connect_to_addr;
228 int connect_to_addrlen =
sizeof(connect_to_addr);
229 if (evutil_parse_sockaddr_port(tor_control_center.c_str(),
230 (
struct sockaddr *)&connect_to_addr,
231 &connect_to_addrlen) < 0) {
232 LogPrintf(
"tor: Error parsing socket address %s\n", tor_control_center);
237 b_conn = bufferevent_socket_new(
base, -1, BEV_OPT_CLOSE_ON_FREE);
243 bufferevent_enable(
b_conn, EV_READ | EV_WRITE);
248 if (bufferevent_socket_connect(
b_conn, (
struct sockaddr *)&connect_to_addr,
249 connect_to_addrlen) < 0) {
250 LogPrintf(
"tor: Error connecting to address %s\n", tor_control_center);
268 struct evbuffer *buf = bufferevent_get_output(
b_conn);
272 evbuffer_add(buf, cmd.data(), cmd.size());
273 evbuffer_add(buf,
"\r\n", 2);
288 while (ptr < s.size() && s[ptr] !=
' ') {
289 type.push_back(s[ptr]);
292 if (ptr < s.size()) {
296 return make_pair(type, s.substr(ptr));
308 std::map<std::string, std::string> mapping;
310 while (ptr < s.size()) {
311 std::string key, value;
312 while (ptr < s.size() && s[ptr] !=
'=' && s[ptr] !=
' ') {
313 key.push_back(s[ptr]);
317 if (ptr == s.size()) {
318 return std::map<std::string, std::string>();
327 if (ptr < s.size() && s[ptr] ==
'"') {
330 bool escape_next =
false;
331 while (ptr < s.size() && (escape_next || s[ptr] !=
'"')) {
333 escape_next = (s[ptr] ==
'\\' && !escape_next);
334 value.push_back(s[ptr]);
338 if (ptr == s.size()) {
339 return std::map<std::string, std::string>();
355 std::string escaped_value;
356 for (
size_t i = 0; i < value.size(); ++i) {
357 if (value[i] ==
'\\') {
363 if (value[i] ==
'n') {
364 escaped_value.push_back(
'\n');
365 }
else if (value[i] ==
't') {
366 escaped_value.push_back(
'\t');
367 }
else if (value[i] ==
'r') {
368 escaped_value.push_back(
'\r');
369 }
else if (
'0' <= value[i] && value[i] <=
'7') {
374 for (j = 1; j < 3 && (i + j) < value.size() &&
375 '0' <= value[i + j] && value[i + j] <=
'7';
381 if (j == 3 && value[i] >
'3') {
384 escaped_value.push_back(
385 strtol(value.substr(i, j).c_str(), NULL, 8));
389 escaped_value.push_back(value[i]);
392 escaped_value.push_back(value[i]);
395 value = escaped_value;
399 while (ptr < s.size() && s[ptr] !=
' ') {
400 value.push_back(s[ptr]);
404 if (ptr < s.size() && s[ptr] ==
' ') {
408 mapping[key] = value;
422 const std::string &tor_control_center,
464 static void reconnect_cb(evutil_socket_t fd,
short what,
void *arg);
468 const std::string &tor_control_center,
470 : base(_base), m_tor_control_center(tor_control_center), conn(base),
475 "tor: Failed to create event for reconnection: out of memory?\n");
480 std::placeholders::_1),
482 std::placeholders::_1))) {
483 LogPrintf(
"tor: Initiating connection to Tor control port %s failed\n",
507 if (reply.
code == 250) {
509 for (
const std::string &s : reply.
lines) {
511 std::map<std::string, std::string>::iterator i;
512 if ((i = m.find(
"ServiceID")) != m.end()) {
515 if ((i = m.find(
"PrivateKey")) != m.end()) {
520 LogPrintf(
"tor: Error parsing ADD_ONION parameters:\n");
521 for (
const std::string &s : reply.
lines) {
534 LogPrintf(
"tor: Error writing service private key to %s\n",
539 }
else if (reply.
code == 510) {
540 LogPrintf(
"tor: Add onion failed with unrecognized command (You "
541 "probably need to upgrade Tor)\n");
543 LogPrintf(
"tor: Add onion failed; error code %d\n", reply.
code);
549 if (reply.
code == 250) {
561 const bool onion_allowed_by_onlynet{
563 std::any_of(onlynets.begin(), onlynets.end(),
565 return ParseNetwork(n) == NET_ONION;
568 if (onion_allowed_by_onlynet) {
592 std::placeholders::_1, std::placeholders::_2));
594 LogPrintf(
"tor: Authentication failed\n");
614static std::vector<uint8_t>
616 const std::vector<uint8_t> &clientNonce,
617 const std::vector<uint8_t> &serverNonce) {
618 CHMAC_SHA256 computeHash((
const uint8_t *)key.data(), key.size());
620 computeHash.
Write(cookie.data(), cookie.size());
621 computeHash.
Write(clientNonce.data(), clientNonce.size());
622 computeHash.
Write(serverNonce.data(), serverNonce.size());
623 computeHash.
Finalize(computedHash.data());
629 if (reply.
code == 250) {
631 "tor: SAFECOOKIE authentication challenge successful\n");
632 std::pair<std::string, std::string> l =
634 if (l.first ==
"AUTHCHALLENGE") {
635 std::map<std::string, std::string> m =
638 LogPrintf(
"tor: Error parsing AUTHCHALLENGE parameters: %s\n",
642 std::vector<uint8_t> serverHash =
ParseHex(m[
"SERVERHASH"]);
643 std::vector<uint8_t> serverNonce =
ParseHex(m[
"SERVERNONCE"]);
645 "tor: AUTHCHALLENGE ServerHash %s ServerNonce %s\n",
647 if (serverNonce.size() != 32) {
649 "tor: ServerNonce is not 32 bytes, as required by spec\n");
655 if (computedServerHash != serverHash) {
656 LogPrintf(
"tor: ServerHash %s does not match expected "
666 std::placeholders::_1,
667 std::placeholders::_2));
669 LogPrintf(
"tor: Invalid reply to AUTHCHALLENGE\n");
672 LogPrintf(
"tor: SAFECOOKIE authentication challenge failed\n");
678 if (reply.
code == 250) {
679 std::set<std::string> methods;
680 std::string cookiefile;
687 for (
const std::string &s : reply.
lines) {
689 if (l.first ==
"AUTH") {
690 std::map<std::string, std::string> m =
692 std::map<std::string, std::string>::iterator i;
693 if ((i = m.find(
"METHODS")) != m.end()) {
694 std::vector<std::string> m_vec =
696 methods = std::set<std::string>(m_vec.begin(), m_vec.end());
698 if ((i = m.find(
"COOKIEFILE")) != m.end()) {
699 cookiefile = i->second;
701 }
else if (l.first ==
"VERSION") {
702 std::map<std::string, std::string> m =
704 std::map<std::string, std::string>::iterator i;
705 if ((i = m.find(
"Tor")) != m.end()) {
711 for (
const std::string &s : methods) {
721 std::string torpassword =
gArgs.
GetArg(
"-torpassword",
"");
722 if (!torpassword.empty()) {
723 if (methods.count(
"HASHEDPASSWORD")) {
725 "tor: Using HASHEDPASSWORD authentication\n");
727 _conn.
Command(
"AUTHENTICATE \"" + torpassword +
"\"",
729 std::placeholders::_1,
730 std::placeholders::_2));
732 LogPrintf(
"tor: Password provided with -torpassword, but "
733 "HASHEDPASSWORD authentication is not available\n");
735 }
else if (methods.count(
"NULL")) {
738 this, std::placeholders::_1,
739 std::placeholders::_2));
740 }
else if (methods.count(
"SAFECOOKIE")) {
743 "tor: Using SAFECOOKIE authentication, "
744 "reading cookie authentication from %s\n",
746 std::pair<bool, std::string> status_cookie =
748 if (status_cookie.first &&
753 cookie = std::vector<uint8_t>(status_cookie.second.begin(),
754 status_cookie.second.end());
759 std::placeholders::_1,
760 std::placeholders::_2));
762 if (status_cookie.first) {
763 LogPrintf(
"tor: Authentication cookie %s is not exactly %i "
764 "bytes, as is required by the spec\n",
767 LogPrintf(
"tor: Authentication cookie %s could not be "
768 "opened (check permissions)\n",
772 }
else if (methods.count(
"HASHEDPASSWORD")) {
773 LogPrintf(
"tor: The only supported authentication mechanism left "
774 "is password, but no password provided with "
777 LogPrintf(
"tor: No supported authentication method\n");
780 LogPrintf(
"tor: Requesting protocol info failed\n");
788 if (!_conn.
Command(
"PROTOCOLINFO 1",
790 std::placeholders::_1,
791 std::placeholders::_2))) {
792 LogPrintf(
"tor: Error sending initial protocolinfo command\n");
807 "tor: Not connected to Tor control port %s, trying to reconnect\n",
824 std::placeholders::_1),
826 std::placeholders::_1))) {
828 "tor: Re-initiating connection to Tor control port %s failed\n",
848 onion_service_target);
850 event_base_dispatch(
gBase);
856 evthread_use_windows_threads();
858 evthread_use_pthreads();
860 gBase = event_base_new();
862 LogPrintf(
"tor: Unable to create event_base\n");
876 gBase, -1, EV_TIMEOUT,
877 [](evutil_socket_t,
short,
void *) { event_base_loopbreak(
gBase); },
885 event_base_free(
gBase);
891 struct in_addr onion_service_target;
892 onion_service_target.s_addr = htonl(INADDR_LOOPBACK);
const CChainParams & Params()
Return the currently selected parameters.
const CBaseChainParams & BaseParams()
Return the currently selected parameters.
std::vector< std::string > GetArgs(const std::string &strArg) const
Return a vector of strings of the given argument.
fs::path GetDataDirNet() const
Get data directory path with appended network identifier.
bool IsArgSet(const std::string &strArg) const
Return true if the given argument has been manually set.
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
uint16_t OnionServiceTargetPort() const
A hasher class for HMAC-SHA-256.
static const size_t OUTPUT_SIZE
CHMAC_SHA256 & Write(const uint8_t *data, size_t len)
void Finalize(uint8_t hash[OUTPUT_SIZE])
A combination of a network address (CNetAddr) and a (TCP) port.
std::string ToStringAddrPort() const
Low-level handling for Tor control connection.
TorControlReply message
Message being received.
std::deque< ReplyHandlerCB > reply_handlers
Response handlers.
bool Connect(const std::string &tor_control_center, const ConnectionCB &connected, const ConnectionCB &disconnected)
Connect to a Tor control port.
TorControlConnection(struct event_base *base)
Create a new TorControlConnection.
bool Command(const std::string &cmd, const ReplyHandlerCB &reply_handler)
Send a command, register a handler for the reply.
std::function< void(TorControlConnection &)> connected
Callback when ready for use.
boost::signals2::signal< void(TorControlConnection &, const TorControlReply &)> async_handler
Response handlers for async replies.
static void readcb(struct bufferevent *bev, void *ctx)
Libevent handlers: internal.
std::function< void(TorControlConnection &)> disconnected
Callback when connection lost.
static void eventcb(struct bufferevent *bev, short what, void *ctx)
std::function< void(TorControlConnection &, const TorControlReply &)> ReplyHandlerCB
void Disconnect()
Disconnect from Tor control port.
struct bufferevent * b_conn
Connection to control socket.
struct event_base * base
Libevent event base.
std::function< void(TorControlConnection &)> ConnectionCB
Reply from Tor, can be single or multi-line.
std::vector< std::string > lines
Controller that connects to Tor control socket, authenticate, then create and maintain an ephemeral o...
TorControlConnection conn
static void reconnect_cb(evutil_socket_t fd, short what, void *arg)
Callback for reconnect timer.
fs::path GetPrivateKeyFile()
Get name fo file to store private key in.
std::vector< uint8_t > clientNonce
ClientNonce for SAFECOOKIE auth.
void connected_cb(TorControlConnection &conn)
Callback after successful connection.
void add_onion_cb(TorControlConnection &conn, const TorControlReply &reply)
Callback for ADD_ONION result.
const std::string m_tor_control_center
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.
std::vector< uint8_t > cookie
Cookie for SAFECOOKIE auth.
struct event * reconnect_ev
TorController(struct event_base *base, const std::string &tor_control_center, const CService &target)
void auth_cb(TorControlConnection &conn, const TorControlReply &reply)
Callback for AUTHENTICATE result.
void Reconnect()
Reconnect, after getting disconnected.
void protocolinfo_cb(TorControlConnection &conn, const TorControlReply &reply)
Callback for PROTOCOLINFO result.
Path class wrapper to block calls to the fs::path(std::string) implicit constructor and the fs::path:...
std::string HexStr(const Span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
#define LogPrint(category,...)
static std::string PathToString(const path &path)
Convert path object to byte string.
static path PathFromString(const std::string &string)
Convert byte string to path object.
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, const std::string &search, const std::string &substitute)
void RemoveLocal(const CService &addr)
void SetReachable(enum Network net, bool reachable)
Mark a network as reachable or unreachable (no automatic connects to it)
bool AddLocal(const CService &addr, int nScore)
@ NET_ONION
TOR (v2 or v3)
bool SetProxy(enum Network net, const Proxy &addrProxy)
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(Span< uint8_t > bytes) noexcept
================== BASE RANDOMNESS GENERATION FUNCTIONS ====================
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=std::numeric_limits< size_t >::max())
Read full contents of a file and return them in a std::string.
static uint16_t GetDefaultPort()
struct timeval MillisToTimeval(int64_t nTimeout)
Convert milliseconds to a struct timeval for e.g.
static const std::string TOR_SAFE_CLIENTKEY
For computing clientHash in SAFECOOKIE.
CService DefaultOnionServiceTarget()
static std::vector< uint8_t > ComputeResponse(const std::string &key, const std::vector< uint8_t > &cookie, const std::vector< uint8_t > &clientNonce, const std::vector< uint8_t > &serverNonce)
Compute Tor SAFECOOKIE response.
static const int MAX_LINE_LENGTH
Maximum length for lines received on TorControlConnection.
static const float RECONNECT_TIMEOUT_EXP
Exponential backoff configuration - growth factor.
const std::string DEFAULT_TOR_CONTROL
Default control port.
static void TorControlThread(CService onion_service_target)
std::pair< std::string, std::string > SplitTorReplyLine(const std::string &s)
static const std::string TOR_SAFE_SERVERKEY
For computing serverHash in SAFECOOKIE.
static const int TOR_COOKIE_SIZE
Tor cookie size (from control-spec.txt)
static struct event_base * gBase
static const int TOR_NONCE_SIZE
Size of client/server nonce for SAFECOOKIE.
void InterruptTorControl()
std::map< std::string, std::string > ParseTorReplyMapping(const std::string &s)
Parse reply arguments in the form 'METHODS=COOKIE,SAFECOOKIE COOKIEFILE=".../control_auth_cookie"'.
static std::thread torControlThread
static const float RECONNECT_TIMEOUT_START
Exponential backoff configuration - initial timeout in seconds.
void StartTorControl(CService onion_service_target)
template std::vector< std::byte > ParseHex(std::string_view)
std::string SanitizeString(std::string_view str, int rule)
Remove unsafe chars.