You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
8.5 KiB
268 lines
8.5 KiB
// Copyright (c) 2015-2017 The Bitcoin Core developers |
|
// Distributed under the MIT software license, see the accompanying |
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php. |
|
|
|
#include <httprpc.h> |
|
|
|
#include <base58.h> |
|
#include <chainparams.h> |
|
#include <httpserver.h> |
|
#include <rpc/protocol.h> |
|
#include <rpc/server.h> |
|
#include <random.h> |
|
#include <sync.h> |
|
#include <util.h> |
|
#include <utilstrencodings.h> |
|
#include <ui_interface.h> |
|
#include <crypto/hmac_sha256.h> |
|
#include <stdio.h> |
|
|
|
#include <memory> |
|
|
|
#include <boost/algorithm/string.hpp> // boost::trim |
|
|
|
/** WWW-Authenticate to present with 401 Unauthorized response */ |
|
static const char* WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\""; |
|
|
|
/** Simple one-shot callback timer to be used by the RPC mechanism to e.g. |
|
* re-lock the wallet. |
|
*/ |
|
class HTTPRPCTimer : public RPCTimerBase |
|
{ |
|
public: |
|
HTTPRPCTimer(struct event_base* eventBase, std::function<void(void)>& func, int64_t millis) : |
|
ev(eventBase, false, func) |
|
{ |
|
struct timeval tv; |
|
tv.tv_sec = millis/1000; |
|
tv.tv_usec = (millis%1000)*1000; |
|
ev.trigger(&tv); |
|
} |
|
private: |
|
HTTPEvent ev; |
|
}; |
|
|
|
class HTTPRPCTimerInterface : public RPCTimerInterface |
|
{ |
|
public: |
|
explicit HTTPRPCTimerInterface(struct event_base* _base) : base(_base) |
|
{ |
|
} |
|
const char* Name() override |
|
{ |
|
return "HTTP"; |
|
} |
|
RPCTimerBase* NewTimer(std::function<void(void)>& func, int64_t millis) override |
|
{ |
|
return new HTTPRPCTimer(base, func, millis); |
|
} |
|
private: |
|
struct event_base* base; |
|
}; |
|
|
|
|
|
/* Pre-base64-encoded authentication token */ |
|
static std::string strRPCUserColonPass; |
|
/* Stored RPC timer interface (for unregistration) */ |
|
static std::unique_ptr<HTTPRPCTimerInterface> httpRPCTimerInterface; |
|
|
|
static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id) |
|
{ |
|
// Send error reply from json-rpc error object |
|
int nStatus = HTTP_INTERNAL_SERVER_ERROR; |
|
int code = find_value(objError, "code").get_int(); |
|
|
|
if (code == RPC_INVALID_REQUEST) |
|
nStatus = HTTP_BAD_REQUEST; |
|
else if (code == RPC_METHOD_NOT_FOUND) |
|
nStatus = HTTP_NOT_FOUND; |
|
|
|
// Cryptonote error message, the status code is OK. |
|
UniValue cn_code = find_value(objError, "cn_code"); |
|
if (!cn_code.isNull()) { |
|
nStatus = HTTP_OK; |
|
} |
|
|
|
std::string strReply = JSONRPCReply(NullUniValue, objError, id); |
|
|
|
req->WriteHeader("Content-Type", "application/json"); |
|
req->WriteReply(nStatus, strReply); |
|
} |
|
|
|
//This function checks username and password against -rpcauth |
|
//entries from config file. |
|
static bool multiUserAuthorized(std::string strUserPass) |
|
{ |
|
if (strUserPass.find(':') == std::string::npos) { |
|
return false; |
|
} |
|
std::string strUser = strUserPass.substr(0, strUserPass.find(':')); |
|
std::string strPass = strUserPass.substr(strUserPass.find(':') + 1); |
|
|
|
for (const std::string& strRPCAuth : gArgs.GetArgs("-rpcauth")) { |
|
//Search for multi-user login/pass "rpcauth" from config |
|
std::vector<std::string> vFields; |
|
boost::split(vFields, strRPCAuth, boost::is_any_of(":$")); |
|
if (vFields.size() != 3) { |
|
//Incorrect formatting in config file |
|
continue; |
|
} |
|
|
|
std::string strName = vFields[0]; |
|
if (!TimingResistantEqual(strName, strUser)) { |
|
continue; |
|
} |
|
|
|
std::string strSalt = vFields[1]; |
|
std::string strHash = vFields[2]; |
|
|
|
static const unsigned int KEY_SIZE = 32; |
|
unsigned char out[KEY_SIZE]; |
|
|
|
CHMAC_SHA256(reinterpret_cast<const unsigned char*>(strSalt.c_str()), strSalt.size()).Write(reinterpret_cast<const unsigned char*>(strPass.c_str()), strPass.size()).Finalize(out); |
|
std::vector<unsigned char> hexvec(out, out+KEY_SIZE); |
|
std::string strHashFromPass = HexStr(hexvec); |
|
|
|
if (TimingResistantEqual(strHashFromPass, strHash)) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUsernameOut) |
|
{ |
|
if (strRPCUserColonPass.empty()) // Belt-and-suspenders measure if InitRPCAuthentication was not called |
|
return false; |
|
if (strAuth.substr(0, 6) != "Basic ") |
|
return false; |
|
std::string strUserPass64 = strAuth.substr(6); |
|
boost::trim(strUserPass64); |
|
std::string strUserPass = DecodeBase64(strUserPass64); |
|
|
|
if (strUserPass.find(':') != std::string::npos) |
|
strAuthUsernameOut = strUserPass.substr(0, strUserPass.find(':')); |
|
|
|
//Check if authorized under single-user field |
|
if (TimingResistantEqual(strUserPass, strRPCUserColonPass)) { |
|
return true; |
|
} |
|
return multiUserAuthorized(strUserPass); |
|
} |
|
|
|
static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &) |
|
{ |
|
// JSONRPC handles only POST |
|
if (req->GetRequestMethod() != HTTPRequest::POST) { |
|
req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests"); |
|
return false; |
|
} |
|
// Check authorization |
|
std::pair<bool, std::string> authHeader = req->GetHeader("authorization"); |
|
if (!authHeader.first) { |
|
req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); |
|
req->WriteReply(HTTP_UNAUTHORIZED); |
|
return false; |
|
} |
|
|
|
JSONRPCRequest jreq; |
|
if (!RPCAuthorized(authHeader.second, jreq.authUser)) { |
|
LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", req->GetPeer().ToString()); |
|
|
|
/* Deter brute-forcing |
|
If this results in a DoS the user really |
|
shouldn't have their RPC port exposed. */ |
|
MilliSleep(250); |
|
|
|
req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); |
|
req->WriteReply(HTTP_UNAUTHORIZED); |
|
return false; |
|
} |
|
|
|
try { |
|
// Parse request |
|
UniValue valRequest; |
|
if (!valRequest.read(req->ReadBody())) |
|
throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); |
|
|
|
// Set the URI |
|
jreq.URI = req->GetURI(); |
|
|
|
std::string strReply; |
|
// singleton request |
|
if (valRequest.isObject()) { |
|
jreq.parse(valRequest); |
|
|
|
UniValue result = tableRPC.execute(jreq); |
|
|
|
// Send reply |
|
strReply = JSONRPCReply(result, NullUniValue, jreq.id); |
|
|
|
// array of requests |
|
} else if (valRequest.isArray()) |
|
strReply = JSONRPCExecBatch(jreq, valRequest.get_array()); |
|
else |
|
throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); |
|
|
|
req->WriteHeader("Content-Type", "application/json"); |
|
req->WriteReply(HTTP_OK, strReply); |
|
} catch (const UniValue& objError) { |
|
JSONErrorReply(req, objError, jreq.id); |
|
return false; |
|
} catch (const std::exception& e) { |
|
JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
static bool InitRPCAuthentication() |
|
{ |
|
if (gArgs.GetArg("-rpcpassword", "") == "") |
|
{ |
|
LogPrintf("No rpcpassword set - using random cookie authentication\n"); |
|
if (!GenerateAuthCookie(&strRPCUserColonPass)) { |
|
uiInterface.ThreadSafeMessageBox( |
|
_("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode |
|
"", CClientUIInterface::MSG_ERROR); |
|
return false; |
|
} |
|
} else { |
|
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/rpcuser for rpcauth auth generation.\n"); |
|
strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", ""); |
|
} |
|
return true; |
|
} |
|
|
|
bool StartHTTPRPC() |
|
{ |
|
LogPrint(BCLog::RPC, "Starting HTTP RPC server\n"); |
|
if (!InitRPCAuthentication()) |
|
return false; |
|
|
|
RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); |
|
RegisterHTTPHandler("/json_rpc", true, HTTPReq_JSONRPC); |
|
#ifdef ENABLE_WALLET |
|
// ifdef can be removed once we switch to better endpoint support and API versioning |
|
RegisterHTTPHandler("/wallet/", false, HTTPReq_JSONRPC); |
|
#endif |
|
assert(EventBase()); |
|
httpRPCTimerInterface = MakeUnique<HTTPRPCTimerInterface>(EventBase()); |
|
RPCSetTimerInterface(httpRPCTimerInterface.get()); |
|
return true; |
|
} |
|
|
|
void InterruptHTTPRPC() |
|
{ |
|
LogPrint(BCLog::RPC, "Interrupting HTTP RPC server\n"); |
|
} |
|
|
|
void StopHTTPRPC() |
|
{ |
|
LogPrint(BCLog::RPC, "Stopping HTTP RPC server\n"); |
|
UnregisterHTTPHandler("/", true); |
|
if (httpRPCTimerInterface) { |
|
RPCUnsetTimerInterface(httpRPCTimerInterface.get()); |
|
httpRPCTimerInterface.reset(); |
|
} |
|
}
|
|
|