Changeset View
Changeset View
Standalone View
Standalone View
src/torcontrol.cpp
// Copyright (c) 2015-2016 The Bitcoin Core developers | // Copyright (c) 2015-2016 The Bitcoin Core developers | ||||
// Copyright (c) 2017 The Zcash developers | |||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include <torcontrol.h> | #include <torcontrol.h> | ||||
#include <crypto/hmac_sha256.h> | #include <crypto/hmac_sha256.h> | ||||
#include <net.h> | #include <net.h> | ||||
#include <netbase.h> | #include <netbase.h> | ||||
▲ Show 20 Lines • Show All 255 Lines • ▼ Show 20 Lines | bool TorControlConnection::Command(const std::string &cmd, | ||||
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=...'. | ||||
* Grammar is implicitly defined in https://spec.torproject.org/control-spec by | |||||
* the server reply formats for PROTOCOLINFO (S3.21) and AUTHCHALLENGE (S3.24). | |||||
*/ | */ | ||||
static std::pair<std::string, std::string> | 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()) { | if (ptr < s.size()) { | ||||
// skip ' ' | // skip ' ' | ||||
++ptr; | ++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"'. | ||||
* Returns a map of keys to values, or an empty map if there was an error. | |||||
* Grammar is implicitly defined in https://spec.torproject.org/control-spec by | |||||
* the server reply formats for PROTOCOLINFO (S3.21), AUTHCHALLENGE (S3.24), | |||||
* and ADD_ONION (S3.27). See also sections 2.1 and 2.3. | |||||
*/ | */ | ||||
static std::map<std::string, std::string> | 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] != '=' && 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()) { | if (ptr == s.size()) { | ||||
return std::map<std::string, std::string>(); | return std::map<std::string, std::string>(); | ||||
} | } | ||||
// The remaining string is an OptArguments | |||||
if (s[ptr] == ' ') { | |||||
break; | |||||
} | |||||
// skip '=' | // skip '=' | ||||
++ptr; | ++ptr; | ||||
// Quoted string | // Quoted string | ||||
if (ptr < s.size() && s[ptr] == '"') { | if (ptr < s.size() && s[ptr] == '"') { | ||||
// skip '=' | // skip opening '"' | ||||
++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] == '\\'); | // Repeated backslashes must be interpreted as pairs | ||||
escape_next = (s[ptr] == '\\' && !escape_next); | |||||
value.push_back(s[ptr]); | value.push_back(s[ptr]); | ||||
++ptr; | ++ptr; | ||||
} | } | ||||
// unexpected end of line | // unexpected end of line | ||||
if (ptr == s.size()) { | if (ptr == s.size()) { | ||||
return std::map<std::string, std::string>(); | return std::map<std::string, std::string>(); | ||||
} | } | ||||
// skip closing '"' | // skip closing '"' | ||||
++ptr; | ++ptr; | ||||
/* TODO: unescape value - according to the spec this depends on the | /** | ||||
* context, some strings use C-LogPrintf style escape codes, some | * Unescape value. Per https://spec.torproject.org/control-spec | ||||
* don't. So may be better handled at the call site. | * section 2.1.1: | ||||
*/ | * | ||||
* For future-proofing, controller implementors MAY use the | |||||
* following rules to be compatible with buggy Tor implementations | |||||
* and with future ones that implement the spec as intended: | |||||
* | |||||
* Read \n \t \r and \0 ... \377 as C escapes. | |||||
* Treat a backslash followed by any other character as that | |||||
* character. | |||||
*/ | |||||
std::string escaped_value; | |||||
for (size_t i = 0; i < value.size(); ++i) { | |||||
if (value[i] == '\\') { | |||||
// This will always be valid, because if the QuotedString | |||||
// ended in an odd number of backslashes, then the parser | |||||
// would already have returned above, due to a missing | |||||
// terminating double-quote. | |||||
++i; | |||||
if (value[i] == 'n') { | |||||
escaped_value.push_back('\n'); | |||||
} else if (value[i] == 't') { | |||||
escaped_value.push_back('\t'); | |||||
} else if (value[i] == 'r') { | |||||
escaped_value.push_back('\r'); | |||||
} else if ('0' <= value[i] && value[i] <= '7') { | |||||
size_t j; | |||||
// Octal escape sequences have a limit of three octal | |||||
// digits, but terminate at the first character that is | |||||
// not a valid octal digit if encountered sooner. | |||||
for (j = 1; j < 3 && (i + j) < value.size() && | |||||
'0' <= value[i + j] && value[i + j] <= '7'; | |||||
++j) { | |||||
} | |||||
// Tor restricts first digit to 0-3 for three-digit | |||||
// octals. A leading digit of 4-7 would therefore be | |||||
// interpreted as a two-digit octal. | |||||
if (j == 3 && value[i] > '3') { | |||||
j--; | |||||
} | |||||
escaped_value.push_back( | |||||
strtol(value.substr(i, j).c_str(), NULL, 8)); | |||||
// Account for automatic incrementing at loop end | |||||
i += j - 1; | |||||
} else { | |||||
escaped_value.push_back(value[i]); | |||||
} | |||||
} else { | |||||
escaped_value.push_back(value[i]); | |||||
} | |||||
} | |||||
value = escaped_value; | |||||
} else { | } else { | ||||
// Unquoted value. Note that values can contain '=' at will, just no | // Unquoted value. Note that values can contain '=' at will, just no | ||||
// spaces | // spaces | ||||
while (ptr < s.size() && s[ptr] != ' ') { | while (ptr < s.size() && s[ptr] != ' ') { | ||||
value.push_back(s[ptr]); | value.push_back(s[ptr]); | ||||
++ptr; | ++ptr; | ||||
} | } | ||||
} | } | ||||
Show All 22 Lines | ReadBinaryFile(const fs::path &filename, | ||||
FILE *f = fsbridge::fopen(filename, "rb"); | FILE *f = fsbridge::fopen(filename, "rb"); | ||||
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) { | ||||
// Check for reading errors so we don't return any data if we couldn't | |||||
// read the entire file (or up to maxsize) | |||||
if (ferror(f)) { | |||||
return std::make_pair(false, ""); | |||||
} | |||||
retval.append(buffer, buffer + n); | retval.append(buffer, buffer + n); | ||||
if (retval.size() > maxsize) { | if (retval.size() > maxsize) { | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
fclose(f); | fclose(f); | ||||
return std::make_pair(true, retval); | return std::make_pair(true, retval); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 110 Lines • ▼ Show 20 Lines | if (reply.code == 250) { | ||||
std::map<std::string, std::string>::iterator i; | std::map<std::string, std::string>::iterator i; | ||||
if ((i = m.find("ServiceID")) != m.end()) { | if ((i = m.find("ServiceID")) != m.end()) { | ||||
service_id = i->second; | service_id = i->second; | ||||
} | } | ||||
if ((i = m.find("PrivateKey")) != m.end()) { | if ((i = m.find("PrivateKey")) != m.end()) { | ||||
private_key = i->second; | private_key = i->second; | ||||
} | } | ||||
} | } | ||||
if (service_id.empty()) { | |||||
LogPrintf("tor: Error parsing ADD_ONION parameters:\n"); | |||||
for (const std::string &s : reply.lines) { | |||||
LogPrintf(" %s\n", SanitizeString(s)); | |||||
} | |||||
return; | |||||
} | |||||
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()); | ||||
} else { | } else { | ||||
▲ Show 20 Lines • Show All 76 Lines • ▼ Show 20 Lines | void TorController::authchallenge_cb(TorControlConnection &_conn, | ||||
if (reply.code == 250) { | if (reply.code == 250) { | ||||
LogPrint(BCLog::TOR, | LogPrint(BCLog::TOR, | ||||
"tor: SAFECOOKIE authentication challenge successful\n"); | "tor: SAFECOOKIE authentication challenge successful\n"); | ||||
std::pair<std::string, std::string> l = | std::pair<std::string, std::string> l = | ||||
SplitTorReplyLine(reply.lines[0]); | SplitTorReplyLine(reply.lines[0]); | ||||
if (l.first == "AUTHCHALLENGE") { | if (l.first == "AUTHCHALLENGE") { | ||||
std::map<std::string, std::string> m = | std::map<std::string, std::string> m = | ||||
ParseTorReplyMapping(l.second); | ParseTorReplyMapping(l.second); | ||||
if (m.empty()) { | |||||
LogPrintf("tor: Error parsing AUTHCHALLENGE parameters: %s\n", | |||||
SanitizeString(l.second)); | |||||
return; | |||||
} | |||||
std::vector<uint8_t> serverHash = ParseHex(m["SERVERHASH"]); | std::vector<uint8_t> serverHash = ParseHex(m["SERVERHASH"]); | ||||
std::vector<uint8_t> serverNonce = ParseHex(m["SERVERNONCE"]); | std::vector<uint8_t> serverNonce = ParseHex(m["SERVERNONCE"]); | ||||
LogPrint(BCLog::TOR, | LogPrint(BCLog::TOR, | ||||
"tor: AUTHCHALLENGE ServerHash %s ServerNonce %s\n", | "tor: AUTHCHALLENGE ServerHash %s ServerNonce %s\n", | ||||
HexStr(serverHash), HexStr(serverNonce)); | HexStr(serverHash), HexStr(serverNonce)); | ||||
if (serverNonce.size() != 32) { | if (serverNonce.size() != 32) { | ||||
LogPrintf( | LogPrintf( | ||||
"tor: ServerNonce is not 32 bytes, as required by spec\n"); | "tor: ServerNonce is not 32 bytes, as required by spec\n"); | ||||
▲ Show 20 Lines • Show All 222 Lines • Show Last 20 Lines |