Changeset View
Changeset View
Standalone View
Standalone View
src/torcontrol.cpp
Show First 20 Lines • Show All 127 Lines • ▼ Show 20 Lines | private: | ||||
static void readcb(struct bufferevent *bev, void *ctx); | static void readcb(struct bufferevent *bev, void *ctx); | ||||
static void eventcb(struct bufferevent *bev, short what, void *ctx); | static void eventcb(struct bufferevent *bev, short what, void *ctx); | ||||
}; | }; | ||||
TorControlConnection::TorControlConnection(struct event_base *_base) | TorControlConnection::TorControlConnection(struct event_base *_base) | ||||
: base(_base), b_conn(0) {} | : base(_base), b_conn(0) {} | ||||
TorControlConnection::~TorControlConnection() { | TorControlConnection::~TorControlConnection() { | ||||
if (b_conn) bufferevent_free(b_conn); | if (b_conn) { | ||||
bufferevent_free(b_conn); | |||||
} | |||||
} | } | ||||
void TorControlConnection::readcb(struct bufferevent *bev, void *ctx) { | void TorControlConnection::readcb(struct bufferevent *bev, void *ctx) { | ||||
TorControlConnection *self = (TorControlConnection *)ctx; | TorControlConnection *self = (TorControlConnection *)ctx; | ||||
struct evbuffer *input = bufferevent_get_input(bev); | struct evbuffer *input = bufferevent_get_input(bev); | ||||
size_t n_read_out = 0; | size_t n_read_out = 0; | ||||
char *line; | char *line; | ||||
assert(input); | assert(input); | ||||
// If there is not a whole line to read, evbuffer_readln returns nullptr | // If there is not a whole line to read, evbuffer_readln returns nullptr | ||||
while ((line = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF)) != | while ((line = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF)) != | ||||
nullptr) { | nullptr) { | ||||
std::string s(line, n_read_out); | std::string s(line, n_read_out); | ||||
free(line); | free(line); | ||||
// Short line | // Short line | ||||
if (s.size() < 4) continue; | if (s.size() < 4) { | ||||
continue; | |||||
} | |||||
// <status>(-|+| )<data><CRLF> | // <status>(-|+| )<data><CRLF> | ||||
self->message.code = atoi(s.substr(0, 3)); | self->message.code = atoi(s.substr(0, 3)); | ||||
self->message.lines.push_back(s.substr(4)); | self->message.lines.push_back(s.substr(4)); | ||||
// '-','+' or ' ' | // '-','+' or ' ' | ||||
char ch = s[3]; | char ch = s[3]; | ||||
if (ch == ' ') { | if (ch == ' ') { | ||||
// Final line, dispatch reply and clean up | // Final line, dispatch reply and clean up | ||||
if (self->message.code >= 600) { | if (self->message.code >= 600) { | ||||
Show All 26 Lines | |||||
void TorControlConnection::eventcb(struct bufferevent *bev, short what, | void TorControlConnection::eventcb(struct bufferevent *bev, short what, | ||||
void *ctx) { | void *ctx) { | ||||
TorControlConnection *self = (TorControlConnection *)ctx; | TorControlConnection *self = (TorControlConnection *)ctx; | ||||
if (what & BEV_EVENT_CONNECTED) { | if (what & BEV_EVENT_CONNECTED) { | ||||
LogPrint(BCLog::TOR, "tor: Successfully connected!\n"); | LogPrint(BCLog::TOR, "tor: Successfully connected!\n"); | ||||
self->connected(*self); | self->connected(*self); | ||||
} else if (what & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { | } else if (what & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { | ||||
if (what & BEV_EVENT_ERROR) | if (what & BEV_EVENT_ERROR) { | ||||
LogPrint(BCLog::TOR, | LogPrint(BCLog::TOR, | ||||
"tor: Error connecting to Tor control socket\n"); | "tor: Error connecting to Tor control socket\n"); | ||||
else | } else { | ||||
LogPrint(BCLog::TOR, "tor: End of stream\n"); | LogPrint(BCLog::TOR, "tor: End of stream\n"); | ||||
} | |||||
self->Disconnect(); | self->Disconnect(); | ||||
self->disconnected(*self); | self->disconnected(*self); | ||||
} | } | ||||
} | } | ||||
bool TorControlConnection::Connect(const std::string &target, | bool TorControlConnection::Connect(const std::string &target, | ||||
const ConnectionCB &_connected, | const ConnectionCB &_connected, | ||||
const ConnectionCB &_disconnected) { | const ConnectionCB &_disconnected) { | ||||
if (b_conn) Disconnect(); | if (b_conn) { | ||||
Disconnect(); | |||||
} | |||||
// Parse target address:port | // Parse target address:port | ||||
struct sockaddr_storage connect_to_addr; | struct sockaddr_storage connect_to_addr; | ||||
int connect_to_addrlen = sizeof(connect_to_addr); | int connect_to_addrlen = sizeof(connect_to_addr); | ||||
if (evutil_parse_sockaddr_port(target.c_str(), | if (evutil_parse_sockaddr_port(target.c_str(), | ||||
(struct sockaddr *)&connect_to_addr, | (struct sockaddr *)&connect_to_addr, | ||||
&connect_to_addrlen) < 0) { | &connect_to_addrlen) < 0) { | ||||
LogPrintf("tor: Error parsing socket address %s\n", target); | LogPrintf("tor: Error parsing socket address %s\n", target); | ||||
return false; | return false; | ||||
} | } | ||||
// Create a new socket, set up callbacks and enable notification bits | // Create a new socket, set up callbacks and enable notification bits | ||||
b_conn = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); | b_conn = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); | ||||
if (!b_conn) return false; | if (!b_conn) { | ||||
return false; | |||||
} | |||||
bufferevent_setcb(b_conn, TorControlConnection::readcb, nullptr, | bufferevent_setcb(b_conn, TorControlConnection::readcb, nullptr, | ||||
TorControlConnection::eventcb, this); | TorControlConnection::eventcb, this); | ||||
bufferevent_enable(b_conn, EV_READ | EV_WRITE); | bufferevent_enable(b_conn, EV_READ | EV_WRITE); | ||||
this->connected = _connected; | this->connected = _connected; | ||||
this->disconnected = _disconnected; | this->disconnected = _disconnected; | ||||
// Finally, connect to target | // Finally, connect to target | ||||
if (bufferevent_socket_connect(b_conn, (struct sockaddr *)&connect_to_addr, | if (bufferevent_socket_connect(b_conn, (struct sockaddr *)&connect_to_addr, | ||||
connect_to_addrlen) < 0) { | connect_to_addrlen) < 0) { | ||||
LogPrintf("tor: Error connecting to address %s\n", target); | LogPrintf("tor: Error connecting to address %s\n", target); | ||||
return false; | return false; | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
bool TorControlConnection::Disconnect() { | bool TorControlConnection::Disconnect() { | ||||
if (b_conn) bufferevent_free(b_conn); | if (b_conn) { | ||||
bufferevent_free(b_conn); | |||||
} | |||||
b_conn = 0; | b_conn = 0; | ||||
return true; | return true; | ||||
} | } | ||||
bool TorControlConnection::Command(const std::string &cmd, | bool TorControlConnection::Command(const std::string &cmd, | ||||
const ReplyHandlerCB &reply_handler) { | const ReplyHandlerCB &reply_handler) { | ||||
if (!b_conn) return false; | if (!b_conn) { | ||||
return false; | |||||
} | |||||
struct evbuffer *buf = bufferevent_get_output(b_conn); | struct evbuffer *buf = bufferevent_get_output(b_conn); | ||||
if (!buf) return false; | if (!buf) { | ||||
return false; | |||||
} | |||||
evbuffer_add(buf, cmd.data(), cmd.size()); | evbuffer_add(buf, cmd.data(), cmd.size()); | ||||
evbuffer_add(buf, "\r\n", 2); | evbuffer_add(buf, "\r\n", 2); | ||||
reply_handlers.push_back(reply_handler); | reply_handlers.push_back(reply_handler); | ||||
return true; | return true; | ||||
} | } | ||||
/****** General parsing utilities ********/ | /****** General parsing utilities ********/ | ||||
/* Split reply line in the form 'AUTH METHODS=...' into a type | /* Split reply line in the form 'AUTH METHODS=...' into a type | ||||
* 'AUTH' and arguments 'METHODS=...'. | * 'AUTH' and arguments 'METHODS=...'. | ||||
*/ | */ | ||||
static std::pair<std::string, std::string> | static std::pair<std::string, std::string> | ||||
SplitTorReplyLine(const std::string &s) { | SplitTorReplyLine(const std::string &s) { | ||||
size_t ptr = 0; | size_t ptr = 0; | ||||
std::string type; | std::string type; | ||||
while (ptr < s.size() && s[ptr] != ' ') { | while (ptr < s.size() && s[ptr] != ' ') { | ||||
type.push_back(s[ptr]); | type.push_back(s[ptr]); | ||||
++ptr; | ++ptr; | ||||
} | } | ||||
if (ptr < s.size()) ++ptr; // skip ' ' | if (ptr < s.size()) { | ||||
// skip ' ' | |||||
++ptr; | |||||
} | |||||
return make_pair(type, s.substr(ptr)); | return make_pair(type, s.substr(ptr)); | ||||
} | } | ||||
/** | /** | ||||
* Parse reply arguments in the form 'METHODS=COOKIE,SAFECOOKIE | * Parse reply arguments in the form 'METHODS=COOKIE,SAFECOOKIE | ||||
* COOKIEFILE=".../control_auth_cookie"'. | * COOKIEFILE=".../control_auth_cookie"'. | ||||
*/ | */ | ||||
static std::map<std::string, std::string> | static std::map<std::string, std::string> | ||||
ParseTorReplyMapping(const std::string &s) { | ParseTorReplyMapping(const std::string &s) { | ||||
std::map<std::string, std::string> mapping; | std::map<std::string, std::string> mapping; | ||||
size_t ptr = 0; | size_t ptr = 0; | ||||
while (ptr < s.size()) { | while (ptr < s.size()) { | ||||
std::string key, value; | std::string key, value; | ||||
while (ptr < s.size() && s[ptr] != '=') { | while (ptr < s.size() && s[ptr] != '=') { | ||||
key.push_back(s[ptr]); | key.push_back(s[ptr]); | ||||
++ptr; | ++ptr; | ||||
} | } | ||||
// unexpected end of line | // unexpected end of line | ||||
if (ptr == s.size()) return std::map<std::string, std::string>(); | if (ptr == s.size()) { | ||||
return std::map<std::string, std::string>(); | |||||
} | |||||
// skip '=' | // skip '=' | ||||
++ptr; | ++ptr; | ||||
// Quoted string | // Quoted string | ||||
if (ptr < s.size() && s[ptr] == '"') { | if (ptr < s.size() && s[ptr] == '"') { | ||||
// skip '=' | // skip '=' | ||||
++ptr; | ++ptr; | ||||
bool escape_next = false; | bool escape_next = false; | ||||
while (ptr < s.size() && (!escape_next && s[ptr] != '"')) { | while (ptr < s.size() && (!escape_next && s[ptr] != '"')) { | ||||
escape_next = (s[ptr] == '\\'); | escape_next = (s[ptr] == '\\'); | ||||
value.push_back(s[ptr]); | value.push_back(s[ptr]); | ||||
++ptr; | ++ptr; | ||||
} | } | ||||
// unexpected end of line | // unexpected end of line | ||||
if (ptr == s.size()) return std::map<std::string, std::string>(); | if (ptr == s.size()) { | ||||
return std::map<std::string, std::string>(); | |||||
} | |||||
// skip closing '"' | // skip closing '"' | ||||
++ptr; | ++ptr; | ||||
/* TODO: unescape value - according to the spec this depends on the | /* TODO: unescape value - according to the spec this depends on the | ||||
* context, some strings use C-LogPrintf style escape codes, some | * context, some strings use C-LogPrintf style escape codes, some | ||||
* don't. So may be better handled at the call site. | * don't. So may be better handled at the call site. | ||||
*/ | */ | ||||
} else { | } else { | ||||
// Unquoted value. Note that values can contain '=' at will, just no | // Unquoted value. Note that values can contain '=' at will, just no | ||||
Show All 29 Lines | ReadBinaryFile(const fs::path &filename, | ||||
if (f == nullptr) { | if (f == nullptr) { | ||||
return std::make_pair(false, ""); | return std::make_pair(false, ""); | ||||
} | } | ||||
std::string retval; | std::string retval; | ||||
char buffer[128]; | char buffer[128]; | ||||
size_t n; | size_t n; | ||||
while ((n = fread(buffer, 1, sizeof(buffer), f)) > 0) { | while ((n = fread(buffer, 1, sizeof(buffer), f)) > 0) { | ||||
retval.append(buffer, buffer + n); | retval.append(buffer, buffer + n); | ||||
if (retval.size() > maxsize) break; | if (retval.size() > maxsize) { | ||||
break; | |||||
} | |||||
} | } | ||||
fclose(f); | fclose(f); | ||||
return std::make_pair(true, retval); | return std::make_pair(true, retval); | ||||
} | } | ||||
/** | /** | ||||
* Write contents of std::string to a file. | * Write contents of std::string to a file. | ||||
* @return true on success. | * @return true on success. | ||||
*/ | */ | ||||
static bool WriteBinaryFile(const fs::path &filename, const std::string &data) { | static bool WriteBinaryFile(const fs::path &filename, const std::string &data) { | ||||
FILE *f = fsbridge::fopen(filename, "wb"); | FILE *f = fsbridge::fopen(filename, "wb"); | ||||
if (f == nullptr) return false; | if (f == nullptr) { | ||||
return false; | |||||
} | |||||
if (fwrite(data.data(), 1, data.size(), f) != data.size()) { | if (fwrite(data.data(), 1, data.size(), f) != data.size()) { | ||||
fclose(f); | fclose(f); | ||||
return false; | return false; | ||||
} | } | ||||
fclose(f); | fclose(f); | ||||
return true; | return true; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | private: | ||||
static void reconnect_cb(evutil_socket_t fd, short what, void *arg); | static void reconnect_cb(evutil_socket_t fd, short what, void *arg); | ||||
}; | }; | ||||
TorController::TorController(struct event_base *_base, | TorController::TorController(struct event_base *_base, | ||||
const std::string &_target) | const std::string &_target) | ||||
: base(_base), target(_target), conn(base), reconnect(true), | : base(_base), target(_target), conn(base), reconnect(true), | ||||
reconnect_ev(0), reconnect_timeout(RECONNECT_TIMEOUT_START) { | reconnect_ev(0), reconnect_timeout(RECONNECT_TIMEOUT_START) { | ||||
reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); | reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); | ||||
if (!reconnect_ev) | if (!reconnect_ev) { | ||||
LogPrintf( | LogPrintf( | ||||
"tor: Failed to create event for reconnection: out of memory?\n"); | "tor: Failed to create event for reconnection: out of memory?\n"); | ||||
} | |||||
// Start connection attempts immediately | // Start connection attempts immediately | ||||
if (!conn.Connect(_target, | if (!conn.Connect(_target, | ||||
boost::bind(&TorController::connected_cb, this, _1), | boost::bind(&TorController::connected_cb, this, _1), | ||||
boost::bind(&TorController::disconnected_cb, this, _1))) { | boost::bind(&TorController::disconnected_cb, this, _1))) { | ||||
LogPrintf("tor: Initiating connection to Tor control port %s failed\n", | LogPrintf("tor: Initiating connection to Tor control port %s failed\n", | ||||
_target); | _target); | ||||
} | } | ||||
// Read service private key if cached | // Read service private key if cached | ||||
Show All 17 Lines | |||||
void TorController::add_onion_cb(TorControlConnection &_conn, | void TorController::add_onion_cb(TorControlConnection &_conn, | ||||
const TorControlReply &reply) { | const TorControlReply &reply) { | ||||
if (reply.code == 250) { | if (reply.code == 250) { | ||||
LogPrint(BCLog::TOR, "tor: ADD_ONION successful\n"); | LogPrint(BCLog::TOR, "tor: ADD_ONION successful\n"); | ||||
for (const std::string &s : reply.lines) { | for (const std::string &s : reply.lines) { | ||||
std::map<std::string, std::string> m = ParseTorReplyMapping(s); | std::map<std::string, std::string> m = ParseTorReplyMapping(s); | ||||
std::map<std::string, std::string>::iterator i; | std::map<std::string, std::string>::iterator i; | ||||
if ((i = m.find("ServiceID")) != m.end()) service_id = i->second; | if ((i = m.find("ServiceID")) != m.end()) { | ||||
if ((i = m.find("PrivateKey")) != m.end()) private_key = i->second; | service_id = i->second; | ||||
} | |||||
if ((i = m.find("PrivateKey")) != m.end()) { | |||||
private_key = i->second; | |||||
} | |||||
} | } | ||||
service = LookupNumeric(std::string(service_id + ".onion").c_str(), | service = LookupNumeric(std::string(service_id + ".onion").c_str(), | ||||
GetListenPort()); | GetListenPort()); | ||||
LogPrintf("tor: Got service ID %s, advertising service %s\n", | LogPrintf("tor: Got service ID %s, advertising service %s\n", | ||||
service_id, service.ToString()); | service_id, service.ToString()); | ||||
if (WriteBinaryFile(GetPrivateKeyFile(), private_key)) { | if (WriteBinaryFile(GetPrivateKeyFile(), private_key)) { | ||||
LogPrint(BCLog::TOR, "tor: Cached service private key to %s\n", | LogPrint(BCLog::TOR, "tor: Cached service private key to %s\n", | ||||
GetPrivateKeyFile()); | GetPrivateKeyFile()); | ||||
▲ Show 20 Lines • Show All 126 Lines • ▼ Show 20 Lines | if (reply.code == 250) { | ||||
* 250-AUTH METHODS=HASHEDPASSWORD | * 250-AUTH METHODS=HASHEDPASSWORD | ||||
*/ | */ | ||||
for (const std::string &s : reply.lines) { | for (const std::string &s : reply.lines) { | ||||
std::pair<std::string, std::string> l = SplitTorReplyLine(s); | std::pair<std::string, std::string> l = SplitTorReplyLine(s); | ||||
if (l.first == "AUTH") { | if (l.first == "AUTH") { | ||||
std::map<std::string, std::string> m = | std::map<std::string, std::string> m = | ||||
ParseTorReplyMapping(l.second); | ParseTorReplyMapping(l.second); | ||||
std::map<std::string, std::string>::iterator i; | std::map<std::string, std::string>::iterator i; | ||||
if ((i = m.find("METHODS")) != m.end()) | if ((i = m.find("METHODS")) != m.end()) { | ||||
boost::split(methods, i->second, boost::is_any_of(",")); | boost::split(methods, i->second, boost::is_any_of(",")); | ||||
if ((i = m.find("COOKIEFILE")) != m.end()) | } | ||||
if ((i = m.find("COOKIEFILE")) != m.end()) { | |||||
cookiefile = i->second; | cookiefile = i->second; | ||||
} | |||||
} else if (l.first == "VERSION") { | } else if (l.first == "VERSION") { | ||||
std::map<std::string, std::string> m = | std::map<std::string, std::string> m = | ||||
ParseTorReplyMapping(l.second); | ParseTorReplyMapping(l.second); | ||||
std::map<std::string, std::string>::iterator i; | std::map<std::string, std::string>::iterator i; | ||||
if ((i = m.find("Tor")) != m.end()) { | if ((i = m.find("Tor")) != m.end()) { | ||||
LogPrint(BCLog::TOR, "tor: Connected to Tor version %s\n", | LogPrint(BCLog::TOR, "tor: Connected to Tor version %s\n", | ||||
i->second); | i->second); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 68 Lines • ▼ Show 20 Lines | |||||
} | } | ||||
void TorController::connected_cb(TorControlConnection &_conn) { | void TorController::connected_cb(TorControlConnection &_conn) { | ||||
reconnect_timeout = RECONNECT_TIMEOUT_START; | reconnect_timeout = RECONNECT_TIMEOUT_START; | ||||
// First send a PROTOCOLINFO command to figure out what authentication is | // First send a PROTOCOLINFO command to figure out what authentication is | ||||
// expected | // expected | ||||
if (!_conn.Command( | if (!_conn.Command( | ||||
"PROTOCOLINFO 1", | "PROTOCOLINFO 1", | ||||
boost::bind(&TorController::protocolinfo_cb, this, _1, _2))) | boost::bind(&TorController::protocolinfo_cb, this, _1, _2))) { | ||||
LogPrintf("tor: Error sending initial protocolinfo command\n"); | LogPrintf("tor: Error sending initial protocolinfo command\n"); | ||||
} | } | ||||
} | |||||
void TorController::disconnected_cb(TorControlConnection &_conn) { | void TorController::disconnected_cb(TorControlConnection &_conn) { | ||||
// Stop advertising service when disconnected | // Stop advertising service when disconnected | ||||
if (service.IsValid()) RemoveLocal(service); | if (service.IsValid()) { | ||||
RemoveLocal(service); | |||||
} | |||||
service = CService(); | service = CService(); | ||||
if (!reconnect) return; | if (!reconnect) { | ||||
return; | |||||
} | |||||
LogPrint(BCLog::TOR, | LogPrint(BCLog::TOR, | ||||
"tor: Not connected to Tor control port %s, trying to reconnect\n", | "tor: Not connected to Tor control port %s, trying to reconnect\n", | ||||
target); | target); | ||||
// Single-shot timer for reconnect. Use exponential backoff. | // Single-shot timer for reconnect. Use exponential backoff. | ||||
struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0)); | struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0)); | ||||
if (reconnect_ev) event_add(reconnect_ev, &time); | if (reconnect_ev) { | ||||
event_add(reconnect_ev, &time); | |||||
} | |||||
reconnect_timeout *= RECONNECT_TIMEOUT_EXP; | reconnect_timeout *= RECONNECT_TIMEOUT_EXP; | ||||
} | } | ||||
void TorController::Reconnect() { | void TorController::Reconnect() { | ||||
/* Try to reconnect and reestablish if we get booted - for example, Tor may | /* Try to reconnect and reestablish if we get booted - for example, Tor may | ||||
* be restarting. | * be restarting. | ||||
*/ | */ | ||||
if (!conn.Connect(target, | if (!conn.Connect(target, | ||||
▲ Show 20 Lines • Show All 58 Lines • Show Last 20 Lines |