Changeset View
Changeset View
Standalone View
Standalone View
src/httprpc.cpp
Show First 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | RPCTimerBase *NewTimer(std::function<void(void)> &func, | ||||
int64_t millis) override { | int64_t millis) override { | ||||
return new HTTPRPCTimer(base, func, millis); | return new HTTPRPCTimer(base, func, millis); | ||||
} | } | ||||
private: | private: | ||||
struct event_base *base; | struct event_base *base; | ||||
}; | }; | ||||
/* Pre-base64-encoded authentication token */ | |||||
static std::string strRPCUserColonPass; | |||||
/* Stored RPC timer interface (for unregistration) */ | /* Stored RPC timer interface (for unregistration) */ | ||||
static HTTPRPCTimerInterface *httpRPCTimerInterface = 0; | static HTTPRPCTimerInterface *httpRPCTimerInterface = 0; | ||||
/* RPC CORS Domain, allowed Origin */ | |||||
static std::string strRPCCORSDomain; | |||||
static void JSONErrorReply(HTTPRequest *req, const UniValue &objError, | static void JSONErrorReply(HTTPRequest *req, const UniValue &objError, | ||||
const UniValue &id) { | const UniValue &id) { | ||||
// Send error reply from json-rpc error object. | // Send error reply from json-rpc error object. | ||||
int nStatus = HTTP_INTERNAL_SERVER_ERROR; | int nStatus = HTTP_INTERNAL_SERVER_ERROR; | ||||
int code = find_value(objError, "code").get_int(); | int code = find_value(objError, "code").get_int(); | ||||
if (code == RPC_INVALID_REQUEST) | if (code == RPC_INVALID_REQUEST) | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | if (gArgs.IsArgSet("-rpcauth")) { | ||||
if (TimingResistantEqual(strHashFromPass, strHash)) { | if (TimingResistantEqual(strHashFromPass, strHash)) { | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
static bool RPCAuthorized(const std::string &strAuth, | static bool RPCAuthorized(Config &config, const std::string &strAuth, | ||||
std::string &strAuthUsernameOut) { | std::string &strAuthUsernameOut) { | ||||
// Belt-and-suspenders measure if InitRPCAuthentication was not called. | // Belt-and-suspenders measure if InitRPCAuthentication was not called. | ||||
if (strRPCUserColonPass.empty()) { | if (config.GetRPCUserAndPassword().empty()) { | ||||
return false; | return false; | ||||
} | } | ||||
if (strAuth.substr(0, 6) != "Basic ") { | if (strAuth.substr(0, 6) != "Basic ") { | ||||
return false; | return false; | ||||
} | } | ||||
std::string strUserPass64 = strAuth.substr(6); | std::string strUserPass64 = strAuth.substr(6); | ||||
boost::trim(strUserPass64); | boost::trim(strUserPass64); | ||||
std::string strUserPass = DecodeBase64(strUserPass64); | std::string strUserPass = DecodeBase64(strUserPass64); | ||||
if (strUserPass.find(":") != std::string::npos) { | if (strUserPass.find(":") != std::string::npos) { | ||||
strAuthUsernameOut = strUserPass.substr(0, strUserPass.find(":")); | strAuthUsernameOut = strUserPass.substr(0, strUserPass.find(":")); | ||||
} | } | ||||
// Check if authorized under single-user field | // Check if authorized under single-user field | ||||
if (TimingResistantEqual(strUserPass, strRPCUserColonPass)) { | if (TimingResistantEqual(strUserPass, config.GetRPCUserAndPassword())) { | ||||
return true; | return true; | ||||
} | } | ||||
return multiUserAuthorized(strUserPass); | return multiUserAuthorized(strUserPass); | ||||
} | } | ||||
static bool checkCORS(HTTPRequest *req) { | static bool checkCORS(Config &config, HTTPRequest *req) { | ||||
// https://www.w3.org/TR/cors/#resource-requests | // https://www.w3.org/TR/cors/#resource-requests | ||||
// 1. If the Origin header is not present terminate this set of steps. | // 1. If the Origin header is not present terminate this set of steps. | ||||
// The request is outside the scope of this specification. | // The request is outside the scope of this specification. | ||||
std::pair<bool, std::string> origin = req->GetHeader("origin"); | std::pair<bool, std::string> origin = req->GetHeader("origin"); | ||||
if (!origin.first) { | if (!origin.first) { | ||||
return false; | return false; | ||||
} | } | ||||
// 2. If the value of the Origin header is not a case-sensitive match for | // 2. If the value of the Origin header is not a case-sensitive match for | ||||
// any of the values in list of origins do not set any additional headers | // any of the values in list of origins do not set any additional headers | ||||
// and terminate this set of steps. | // and terminate this set of steps. | ||||
// Note: Always matching is acceptable since the list of origins can be | // Note: Always matching is acceptable since the list of origins can be | ||||
// unbounded. | // unbounded. | ||||
if (origin.second != strRPCCORSDomain) { | if (origin.second != config.GetRPCCORSDomain()) { | ||||
return false; | return false; | ||||
} | } | ||||
if (req->GetRequestMethod() == HTTPRequest::OPTIONS) { | if (req->GetRequestMethod() == HTTPRequest::OPTIONS) { | ||||
// 6.2 Preflight Request | // 6.2 Preflight Request | ||||
// In response to a preflight request the resource indicates which | // In response to a preflight request the resource indicates which | ||||
// methods and headers (other than simple methods and simple | // methods and headers (other than simple methods and simple | ||||
// headers) it is willing to handle and whether it supports | // headers) it is willing to handle and whether it supports | ||||
▲ Show 20 Lines • Show All 92 Lines • ▼ Show 20 Lines | static bool checkCORS(Config &config, HTTPRequest *req) { | ||||
req->WriteHeader("Access-Control-Expose-Headers", "WWW-Authenticate"); | req->WriteHeader("Access-Control-Expose-Headers", "WWW-Authenticate"); | ||||
return false; | return false; | ||||
} | } | ||||
static bool HTTPReq_JSONRPC(Config &config, HTTPRequest *req, | static bool HTTPReq_JSONRPC(Config &config, HTTPRequest *req, | ||||
const std::string &) { | const std::string &) { | ||||
// First, check and/or set CORS headers | // First, check and/or set CORS headers | ||||
if (checkCORS(req)) { | if (checkCORS(config, req)) { | ||||
return true; | return true; | ||||
} | } | ||||
// JSONRPC handles only POST | // JSONRPC handles only POST | ||||
if (req->GetRequestMethod() != HTTPRequest::POST) { | if (req->GetRequestMethod() != HTTPRequest::POST) { | ||||
req->WriteReply(HTTP_BAD_METHOD, | req->WriteReply(HTTP_BAD_METHOD, | ||||
"JSONRPC server handles only POST requests"); | "JSONRPC server handles only POST requests"); | ||||
return false; | return false; | ||||
} | } | ||||
// Check authorization | // Check authorization | ||||
std::pair<bool, std::string> authHeader = req->GetHeader("authorization"); | std::pair<bool, std::string> authHeader = req->GetHeader("authorization"); | ||||
if (!authHeader.first) { | if (!authHeader.first) { | ||||
req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); | req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); | ||||
req->WriteReply(HTTP_UNAUTHORIZED); | req->WriteReply(HTTP_UNAUTHORIZED); | ||||
return false; | return false; | ||||
} | } | ||||
JSONRPCRequest jreq; | JSONRPCRequest jreq; | ||||
if (!RPCAuthorized(authHeader.second, jreq.authUser)) { | if (!RPCAuthorized(config, authHeader.second, jreq.authUser)) { | ||||
LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", | LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", | ||||
req->GetPeer().ToString()); | req->GetPeer().ToString()); | ||||
/* Deter brute-forcing. | /* Deter brute-forcing. | ||||
* If this results in a DoS the user really shouldn't have their RPC | * If this results in a DoS the user really shouldn't have their RPC | ||||
* port exposed. */ | * port exposed. */ | ||||
MilliSleep(250); | MilliSleep(250); | ||||
Show All 34 Lines | try { | ||||
return false; | return false; | ||||
} catch (const std::exception &e) { | } catch (const std::exception &e) { | ||||
JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); | JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); | ||||
return false; | return false; | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
static bool InitRPCAuthentication() { | static bool InitRPCAuthentication(Config &config) { | ||||
if (gArgs.GetArg("-rpcpassword", "") == "") { | if (gArgs.GetArg("-rpcpassword", "") == "") { | ||||
LogPrintf("No rpcpassword set - using random cookie authentication\n"); | LogPrintf("No rpcpassword set - using random cookie authentication\n"); | ||||
if (!GenerateAuthCookie(&strRPCUserColonPass)) { | std::string generatedUserAndPassword; | ||||
if (!GenerateAuthCookie(&generatedUserAndPassword)) { | |||||
// Same message as AbortNode. | // Same message as AbortNode. | ||||
uiInterface.ThreadSafeMessageBox( | uiInterface.ThreadSafeMessageBox( | ||||
_("Error: A fatal internal error occurred, see debug.log for " | _("Error: A fatal internal error occurred, see debug.log for " | ||||
"details"), | "details"), | ||||
"", CClientUIInterface::MSG_ERROR); | "", CClientUIInterface::MSG_ERROR); | ||||
return false; | return false; | ||||
} | } | ||||
config.SetRPCUserAndPassword(generatedUserAndPassword); | |||||
} else { | } else { | ||||
LogPrintf("Config options rpcuser and rpcpassword will soon be " | LogPrintf("Config options rpcuser and rpcpassword will soon be " | ||||
"deprecated. Locally-run instances may remove rpcuser to use " | "deprecated. Locally-run instances may remove rpcuser to use " | ||||
"cookie-based auth, or may be replaced with rpcauth. Please " | "cookie-based auth, or may be replaced with rpcauth. Please " | ||||
"see share/rpcuser for rpcauth auth generation.\n"); | "see share/rpcuser for rpcauth auth generation.\n"); | ||||
strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + | config.SetRPCUserAndPassword(gArgs.GetArg("-rpcuser", "") + ":" + | ||||
gArgs.GetArg("-rpcpassword", ""); | gArgs.GetArg("-rpcpassword", "")); | ||||
} | } | ||||
strRPCCORSDomain = gArgs.GetArg("-rpccorsdomain", ""); | config.SetRPCCORSDomain(gArgs.GetArg("-rpccorsdomain", "")); | ||||
return true; | return true; | ||||
} | } | ||||
bool StartHTTPRPC() { | bool StartHTTPRPC(Config &config) { | ||||
LogPrint(BCLog::RPC, "Starting HTTP RPC server\n"); | LogPrint(BCLog::RPC, "Starting HTTP RPC server\n"); | ||||
if (!InitRPCAuthentication()) return false; | if (!InitRPCAuthentication(config)) return false; | ||||
RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); | RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); | ||||
#ifdef ENABLE_WALLET | #ifdef ENABLE_WALLET | ||||
// ifdef can be removed once we switch to better endpoint support and API | // ifdef can be removed once we switch to better endpoint support and API | ||||
// versioning | // versioning | ||||
RegisterHTTPHandler("/wallet/", false, HTTPReq_JSONRPC); | RegisterHTTPHandler("/wallet/", false, HTTPReq_JSONRPC); | ||||
#endif | #endif | ||||
assert(EventBase()); | assert(EventBase()); | ||||
Show All 18 Lines |