From 03e46714466ec3595df062ee6de76e4b5616582d Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Fri, 9 Sep 2016 11:15:09 +0000 Subject: [PATCH] RPC: Allow rpcauth configs to specify a 4th parameter naming a specific wallet --- src/httprpc.cpp | 43 +++++++++++++++++++------------ src/rpc/request.h | 1 + src/rpc/util.cpp | 7 +++++ src/rpc/util.h | 2 ++ src/wallet/rpc/util.cpp | 44 +++++++++++++++++++++++++------- src/wallet/test/wallet_tests.cpp | 3 +++ 6 files changed, 74 insertions(+), 26 deletions(-) diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 4f6089dc11..cb61a746d0 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -102,7 +102,7 @@ static void JSONErrorReply(HTTPRequest* req, UniValue objError, const JSONRPCReq //This function checks username and password against -rpcauth //entries from config file. -static bool multiUserAuthorized(std::string strUserPass) +static bool multiUserAuthorized(std::string strUserPass, std::string& out_wallet_restriction) { if (strUserPass.find(':') == std::string::npos) { return false; @@ -127,13 +127,14 @@ static bool multiUserAuthorized(std::string strUserPass) std::string strHashFromPass = HexStr(hexvec); if (TimingResistantEqual(strHashFromPass, strHash)) { + out_wallet_restriction = (vFields.size() > 3) ? vFields[3] : ""; return true; } } return false; } -static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUsernameOut) +static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUsernameOut, std::string& out_wallet_restriction) { if (strAuth.substr(0, 6) != "Basic ") return false; @@ -149,9 +150,10 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna // Check if authorized under single-user field. // (strRPCUserColonPass is empty when -norpccookiefile is specified). if (!strRPCUserColonPass.empty() && TimingResistantEqual(strUserPass, strRPCUserColonPass)) { + out_wallet_restriction = ""; return true; } - return multiUserAuthorized(strUserPass); + return multiUserAuthorized(strUserPass, out_wallet_restriction); } static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) @@ -172,7 +174,7 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) JSONRPCRequest jreq; jreq.context = context; jreq.peerAddr = req->GetPeer().ToStringAddrPort(); - if (!RPCAuthorized(authHeader.second, jreq.authUser)) { + if (!RPCAuthorized(authHeader.second, jreq.authUser, jreq.m_wallet_restriction)) { LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", jreq.peerAddr); /* Deter brute-forcing @@ -318,17 +320,26 @@ static bool InitRPCAuthentication() LogPrintf("Config options rpcuser and rpcpassword will soon be deprecated. Locally-run instances may remove rpcuser to use cookie-based auth, or may be replaced with rpcauth. Please see share/rpcauth for rpcauth auth generation.\n"); strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", ""); } + constexpr auto AddRPCAuth = [](const std::string& rpcauth) { + std::vector fields{SplitString(rpcauth, ':')}; + if (fields.size() < 2 || fields.size() > 3) { + return false; + } + const std::vector salt_hmac{SplitString(fields[1], '$')}; + if (salt_hmac.size() == 2) { + fields.erase(fields.begin() + 1); + fields.insert(fields.begin() + 1, salt_hmac.begin(), salt_hmac.end()); + g_rpcauth.push_back(fields); + } else { + return false; + } + return true; + }; if (!(gArgs.IsArgNegated("-rpcauth") || (gArgs.GetArgs("-rpcauth").empty() && gArgs.GetArgs("-rpcauthfile").empty()))) { LogPrintf("Using rpcauth authentication.\n"); for (const std::string& rpcauth : gArgs.GetArgs("-rpcauth")) { if (rpcauth.empty()) continue; - std::vector fields{SplitString(rpcauth, ':')}; - const std::vector salt_hmac{SplitString(fields.back(), '$')}; - if (fields.size() == 2 && salt_hmac.size() == 2) { - fields.pop_back(); - fields.insert(fields.end(), salt_hmac.begin(), salt_hmac.end()); - g_rpcauth.push_back(fields); - } else { + if (!AddRPCAuth(rpcauth)) { LogPrintf("Invalid -rpcauth argument.\n"); return false; } @@ -338,13 +349,11 @@ static bool InitRPCAuthentication() file.open(path); if (!file.is_open()) continue; std::string rpcauth; + size_t lineno = 0; while (std::getline(file, rpcauth)) { - std::vector fields{SplitString(rpcauth, ':')}; - const std::vector salt_hmac{SplitString(fields.back(), '$')}; - if (fields.size() == 2 && salt_hmac.size() == 2) { - fields.pop_back(); - fields.insert(fields.end(), salt_hmac.begin(), salt_hmac.end()); - g_rpcauth.push_back(fields); + ++lineno; + if (!AddRPCAuth(rpcauth)) { + LogPrintf("WARNING: Invalid line %s in -rpcauthfile=%s; ignoring\n", lineno, path); } } } diff --git a/src/rpc/request.h b/src/rpc/request.h index 24887e8691..6c08e6ee37 100644 --- a/src/rpc/request.h +++ b/src/rpc/request.h @@ -41,6 +41,7 @@ public: enum Mode { EXECUTE, GET_HELP, GET_ARGS } mode = EXECUTE; std::string URI; std::string authUser; + std::string m_wallet_restriction{"-"}; std::string peerAddr; std::any context; JSONRPCVersion m_json_version = JSONRPCVersion::V1_LEGACY; diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index cc49670198..d0da704fc0 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -1389,3 +1389,10 @@ void PushWarnings(const std::vector& warnings, UniValue& obj) if (warnings.empty()) return; obj.pushKV("warnings", BilingualStringsToUniValue(warnings)); } + +bool GetWalletRestrictionFromJSONRPCRequest(const JSONRPCRequest& request, std::string& out_wallet_allowed) +{ + if (request.m_wallet_restriction.empty()) return false; + out_wallet_allowed = request.m_wallet_restriction; + return true; +} diff --git a/src/rpc/util.h b/src/rpc/util.h index 23024376e0..d55a8b21a3 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -503,4 +503,6 @@ private: void PushWarnings(const UniValue& warnings, UniValue& obj); void PushWarnings(const std::vector& warnings, UniValue& obj); +bool GetWalletRestrictionFromJSONRPCRequest(const JSONRPCRequest& request, std::string& out_wallet_allowed); + #endif // BITCOIN_RPC_UTIL_H diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp index 67b5ae0fe2..6847b6690c 100644 --- a/src/wallet/rpc/util.cpp +++ b/src/wallet/rpc/util.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -75,18 +76,43 @@ std::shared_ptr GetWalletForJSONRPCRequest(const JSONRPCRequest& reques CHECK_NONFATAL(request.mode == JSONRPCRequest::EXECUTE); WalletContext& context = EnsureWalletContext(request.context); - std::string wallet_name; - if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { - std::shared_ptr pwallet = GetWallet(context, wallet_name); - if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); + bool have_wallet_restriction; + std::string authorized_wallet_name; + have_wallet_restriction = GetWalletRestrictionFromJSONRPCRequest(request, authorized_wallet_name); + + bool have_requested_wallet; + std::string requested_wallet_name; + have_requested_wallet = GetWalletNameFromJSONRPCRequest(request, requested_wallet_name); + + std::shared_ptr pwallet; + size_t count{0}; + + if (!have_wallet_restriction) { + // Any wallet is permitted; select by endpoint, or use the sole wallet + if (have_requested_wallet) { + pwallet = GetWallet(context, requested_wallet_name); + } else { + auto wallet = GetDefaultWallet(context, count); + if (wallet) pwallet = wallet; + } + } else if (authorized_wallet_name == "-") { + // Block wallet access always + } else if ((!have_requested_wallet) || requested_wallet_name == authorized_wallet_name) { + // Select specifically the authorized wallet + pwallet = GetWallet(context, authorized_wallet_name); + } + + if (pwallet) { return pwallet; } - size_t count{0}; - auto wallet = GetDefaultWallet(context, count); - if (wallet) return wallet; - - if (count == 0) { + if (have_requested_wallet) { + throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); + } + if (have_wallet_restriction + ? (authorized_wallet_name == "-" || !GetWallet(context, authorized_wallet_name)) + : (count == 0) + ) { throw JSONRPCError( RPC_WALLET_NOT_FOUND, "No wallet is loaded. Load a wallet using loadwallet or create a new one with createwallet. (Note: A default wallet is no longer automatically created)"); } diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 12d5a3b3eb..81e1da09ae 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -238,6 +238,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) keys.push_back(std::move(key)); JSONRPCRequest request; request.context = &context; + request.m_wallet_restriction = ""; request.params.setArray(); request.params.push_back(std::move(keys)); @@ -293,6 +294,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) } JSONRPCRequest request; request.context = &context; + request.m_wallet_restriction = ""; request.params.setArray(); request.params.push_back(backup_file); @@ -311,6 +313,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) context.args = &m_args; JSONRPCRequest request; request.context = &context; + request.m_wallet_restriction = ""; request.params.setArray(); request.params.push_back(backup_file); AddWallet(context, wallet);