diff --git a/src/init.cpp b/src/init.cpp index 7f48ae3..05e49cf 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -469,6 +469,7 @@ std::string HelpMessage(HelpMessageMode mode) #endif strUsage += HelpMessageGroup(_("Debugging/Testing options:")); + strUsage += HelpMessageOpt("-uacomment=", _("Append comment to the user agent string")); if (GetBoolArg("-help-debug", false)) { strUsage += HelpMessageOpt("-checkblockindex", strprintf("Do a full consistency check for mapBlockIndex, setBlockIndexCandidates, chainActive and mapBlocksUnlinked occasionally. Also sets -checkmempool (default: %u)", Params(CBaseChainParams::MAIN).DefaultConsistencyChecks())); strUsage += HelpMessageOpt("-checkmempool=", strprintf("Run checks every transactions (default: %u)", Params(CBaseChainParams::MAIN).DefaultConsistencyChecks())); @@ -1215,6 +1216,21 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) RegisterNodeSignals(GetNodeSignals()); + // sanitize comments per BIP-0014, format user agent and check total size + std::vector uacomments; + for (const std::string& cmt : mapMultiArgs["-uacomment"]) { + if (cmt != SanitizeString(cmt, SAFE_CHARS_UA_COMMENT)) + return InitError(strprintf(_("User Agent comment (%s) contains unsafe characters."), cmt)); + uacomments.push_back(cmt); + } + + // format user agent, check total size + strSubVersion = FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments); + if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) { + return InitError(strprintf(_("Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments."), + strSubVersion.size(), MAX_SUBVERSION_LENGTH)); + } + if (mapArgs.count("-onlynet")) { std::set nets; BOOST_FOREACH (std::string snet, mapMultiArgs["-onlynet"]) { diff --git a/src/main.cpp b/src/main.cpp index 7c43a1c..8130b28 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5531,7 +5531,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, if (!vRecv.empty()) vRecv >> addrFrom >> nNonce; if (!vRecv.empty()) { - vRecv >> LIMITED_STRING(pfrom->strSubVer, 256); + vRecv >> LIMITED_STRING(pfrom->strSubVer, MAX_SUBVERSION_LENGTH); pfrom->cleanSubVer = SanitizeString(pfrom->strSubVer); } if (!vRecv.empty()) diff --git a/src/net.cpp b/src/net.cpp index 6f04ab3..8a34e41 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -86,6 +86,7 @@ static std::vector vhListenSocket; CAddrMan addrman; int nMaxConnections = 125; bool fAddressesInitialized = false; +std::string strSubVersion; vector vNodes; CCriticalSection cs_vNodes; @@ -484,7 +485,7 @@ void CNode::PushVersion() else LogPrint("net", "send version message: version %d, blocks=%d, us=%s, peer=%d\n", PROTOCOL_VERSION, nBestHeight, addrMe.ToString(), id); PushMessage("version", PROTOCOL_VERSION, nLocalServices, nTime, addrYou, addrMe, - nLocalHostNonce, FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, std::vector()), nBestHeight, true); + nLocalHostNonce, strSubVersion, nBestHeight, true); } @@ -1725,7 +1726,7 @@ void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler) CNode::SetBannedSetDirty(false); //no need to write down just read or nonexistent data CNode::SweepBanned(); //sweap out unused entries - // Initialize random numbers. Even when rand() is only usable for trivial use-cases most nodes should have a different + // Initialize random numbers. Even when rand() is only usable for trivial use-cases most nodes should have a different // seed after all the file-IO done at this point. Should be good enough even when nodes are started via scripts. srand(time(NULL)); diff --git a/src/net.h b/src/net.h index 5f26ffe..39cf023 100644 --- a/src/net.h +++ b/src/net.h @@ -52,6 +52,8 @@ static const unsigned int MAX_INV_SZ = 50000; static const unsigned int MAX_ADDR_TO_SEND = 1000; /** Maximum length of incoming protocol messages (no message over 2 MiB is currently acceptable). */ static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = 2 * 1024 * 1024; +/** Maximum length of strSubVer in `version` message */ +static const unsigned int MAX_SUBVERSION_LENGTH = 256; /** -listen default */ static const bool DEFAULT_LISTEN = true; /** -upnp default */ @@ -143,6 +145,9 @@ extern CCriticalSection cs_vAddedNodes; extern NodeId nLastNodeId; extern CCriticalSection cs_nLastNodeId; +/** Subversion as sent to the P2P network in `version` messages */ +extern std::string strSubVersion; + struct LocalServiceInfo { int nScore; int nPort; diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 9b61be3..9bfd135 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -430,8 +430,7 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp) UniValue obj(UniValue::VOBJ); obj.push_back(Pair("version", CLIENT_VERSION)); - obj.push_back(Pair("subversion", - FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, std::vector()))); + obj.push_back(Pair("subversion", strSubVersion)); obj.push_back(Pair("protocolversion", PROTOCOL_VERSION)); obj.push_back(Pair("localservices", strprintf("%016x", nLocalServices))); obj.push_back(Pair("timeoffset", GetTimeOffset())); diff --git a/src/utilstrencodings.cpp b/src/utilstrencodings.cpp index 93db692..c99100f 100644 --- a/src/utilstrencodings.cpp +++ b/src/utilstrencodings.cpp @@ -22,16 +22,21 @@ using namespace std; -string SanitizeString(const string& str) +static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +static const std::string SAFE_CHARS[] = { - /** - * safeChars chosen to allow simple messages/URLs/email addresses, but avoid anything - * even possibly remotely dangerous like & or > - */ - static string safeChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890 .,;_-/:?@()"); - string strResult; - for (std::string::size_type i = 0; i < str.size(); i++) { - if (safeChars.find(str[i]) != std::string::npos) + CHARS_ALPHA_NUM + " .,;-_/:?@()", // SAFE_CHARS_DEFAULT + CHARS_ALPHA_NUM + " .,;-_?@", // SAFE_CHARS_UA_COMMENT + CHARS_ALPHA_NUM + ".-_", // SAFE_CHARS_FILENAME +}; + +std::string SanitizeString(const std::string& str, int rule) +{ + std::string strResult; + for (std::string::size_type i = 0; i < str.size(); i++) + { + if (SAFE_CHARS[rule].find(str[i]) != std::string::npos) strResult.push_back(str[i]); } return strResult; diff --git a/src/utilstrencodings.h b/src/utilstrencodings.h index 5202f26..15dd5d2 100644 --- a/src/utilstrencodings.h +++ b/src/utilstrencodings.h @@ -25,7 +25,22 @@ /** This is needed because the foreach macro can't get over the comma in pair */ #define PAIRTYPE(t1, t2) std::pair -std::string SanitizeString(const std::string& str); +/** Used by SanitizeString() */ +enum SafeChars +{ + SAFE_CHARS_DEFAULT, //!< The full set of allowed chars + SAFE_CHARS_UA_COMMENT, //!< BIP-0014 subset + SAFE_CHARS_FILENAME, //!< Chars allowed in filenames +}; + +/** +* Remove unsafe chars. Safe chars chosen to allow simple messages/URLs/email +* addresses, but avoid anything even possibly remotely dangerous like & or > +* @param[in] str The string to sanitize +* @param[in] rule The set of safe chars to choose (default: least restrictive) +* @return A new string without unsafe chars +*/ +std::string SanitizeString(const std::string& str, int rule = SAFE_CHARS_DEFAULT); std::vector ParseHex(const char* psz); std::vector ParseHex(const std::string& str); signed char HexDigit(char c);