diff --git a/src/httprpc.cpp b/src/httprpc.cpp index c72dbf10bc958..e54ad4bfcc85e 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -240,12 +240,41 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) return true; } +static std::optional StringToOctal(const std::string& str) +{ + unsigned ret = 0; + for (char c : str) { + if (c < '0' || c > '7') return std::nullopt; + ret = (ret << 3) | (c - '0'); + } + return ret; +} + +static auto ConvertPermsToOctal(const std::string& str) noexcept -> std::optional +{ + // Don't permit setting special bits as they're not relevant to cookie files + if (str.length() == 3) return StringToOctal(str); + return std::nullopt; +} + static bool InitRPCAuthentication() { if (gArgs.GetArg("-rpcpassword", "") == "") { LogPrintf("Using random cookie authentication.\n"); - if (!GenerateAuthCookie(&strRPCUserColonPass)) { + + auto cookie_perms{DEFAULT_COOKIE_PERMS}; + auto cookie_perms_arg{gArgs.GetArg("-rpccookieperms")}; + if (cookie_perms_arg) { + auto perms{ConvertPermsToOctal(*cookie_perms_arg)}; + if (!perms) { + LogPrintf("Invalid -rpccookieperms=%s; must be a 3 digit octal number (e.g. 400, 440 or 444).\n", *cookie_perms_arg); + return false; + } + cookie_perms = static_cast(*perms); + } + + if (!GenerateAuthCookie(&strRPCUserColonPass, cookie_perms)) { return false; } } else { diff --git a/src/init.cpp b/src/init.cpp index 39ee9fe60dc05..4a661cdab4b7b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -653,6 +653,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-rpcbind=[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); argsman.AddArg("-rpcdoccheck", strprintf("Throw a non-fatal error at runtime if the documentation for an RPC is incorrect (default: %u)", DEFAULT_RPC_DOC_CHECK), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); argsman.AddArg("-rpccookiefile=", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + argsman.AddArg("-rpccookieperms=", strprintf("Set the permissions on the RPC auth cookie file to the specified octal value on unix systems (default: %u)", PermsToOctalString(DEFAULT_COOKIE_PERMS).c_str()), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcpassword=", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcport=", strprintf("Listen for JSON-RPC connections on (default: %u, testnet: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); argsman.AddArg("-rpcservertimeout=", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp index b7acd62ee3d4b..ef777bea68496 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -82,16 +82,13 @@ static fs::path GetAuthCookieFile(bool temp=false) static bool g_generated_cookie = false; -bool GenerateAuthCookie(std::string *cookie_out) +bool GenerateAuthCookie(std::string* cookie_out, fs::perms cookie_perms) { const size_t COOKIE_SIZE = 32; unsigned char rand_pwd[COOKIE_SIZE]; GetRandBytes(rand_pwd); std::string cookie = COOKIEAUTH_USER + ":" + HexStr(rand_pwd); - /** the umask determines what permissions are used to create this file - - * these are set to 0077 in common/system.cpp. - */ std::ofstream file; fs::path filepath_tmp = GetAuthCookieFile(true); file.open(filepath_tmp); @@ -99,6 +96,18 @@ bool GenerateAuthCookie(std::string *cookie_out) LogPrintf("Unable to open cookie authentication file %s for writing\n", fs::PathToString(filepath_tmp)); return false; } + +#ifdef __unix__ + std::error_code code; + fs::permissions(filepath_tmp, cookie_perms, fs::perm_options::replace, code); + if (code) { + LogPrintf("Unable to set permissions on cookie authentication file %s\n", fs::PathToString(filepath_tmp)); + return false; + } +#else + LogPrintf("Unable to set unix permissions on cookie authentication file on non-Unix systems: %s\n", fs::PathToString(filepath_tmp)); +#endif + file << cookie; file.close(); @@ -109,6 +118,7 @@ bool GenerateAuthCookie(std::string *cookie_out) } g_generated_cookie = true; LogPrintf("Generated RPC authentication cookie %s\n", fs::PathToString(filepath)); + LogPrintf("Permissions used for cookie: %s\n", PermsToString(fs::status(filepath).permissions())); if (cookie_out) *cookie_out = cookie; diff --git a/src/rpc/request.h b/src/rpc/request.h index a682c58d966f0..bc0e206062d1c 100644 --- a/src/rpc/request.h +++ b/src/rpc/request.h @@ -10,6 +10,7 @@ #include #include +#include UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id); @@ -17,7 +18,7 @@ std::string JSONRPCReply(const UniValue& result, const UniValue& error, const Un UniValue JSONRPCError(int code, const std::string& message); /** Generate a new RPC authentication cookie and write it to disk */ -bool GenerateAuthCookie(std::string *cookie_out); +bool GenerateAuthCookie(std::string* cookie_out, fs::perms cookie_perms); /** Read the RPC authentication cookie from disk */ bool GetAuthCookie(std::string *cookie_out); /** Delete RPC authentication cookie from disk */