123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- // Copyright (c) 2015-2016 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 <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:
- 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 HTTPRPCTimerInterface* httpRPCTimerInterface = 0;
-
- 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;
-
- 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);
- #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 = new HTTPRPCTimerInterface(EventBase());
- RPCSetTimerInterface(httpRPCTimerInterface);
- 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);
- delete httpRPCTimerInterface;
- httpRPCTimerInterface = 0;
- }
- }
|