Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2015-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <httprpc.h>7#include <common/args.h>8#include <crypto/hmac_sha256.h>9#include <httpserver.h>10#include <netaddress.h>11#include <rpc/protocol.h>12#include <rpc/server.h>13#include <util/fs.h>14#include <util/fs_helpers.h>15#include <util/log.h>16#include <util/strencodings.h>17#include <util/string.h>18#include <walletinitinterface.h>20#include <algorithm>21#include <iterator>22#include <map>23#include <memory>24#include <optional>25#include <set>26#include <string>27#include <vector>29using util::SplitString;30using util::TrimStringView;32/** WWW-Authenticate to present with 401 Unauthorized response */33static const char* WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\"";35/* List of -rpcauth values */36static std::vector<std::vector<std::string>> g_rpcauth;37/* RPC Auth Whitelist */38static std::map<std::string, std::set<std::string>> g_rpc_whitelist;39static bool g_rpc_whitelist_default = false;41static UniValue JSONErrorReply(UniValue objError, const JSONRPCRequest& jreq, HTTPStatusCode& nStatus)42{43// HTTP errors should never be returned if JSON-RPC v2 was requested. This44// function should only be called when a v1 request fails or when a request45// cannot be parsed, so the version is unknown.46Assume(jreq.m_json_version != JSONRPCVersion::V2);48// Send error reply from json-rpc error object49nStatus = HTTP_INTERNAL_SERVER_ERROR;50int code = objError.find_value("code").getInt<int>();52if (code == RPC_INVALID_REQUEST)53nStatus = HTTP_BAD_REQUEST;54else if (code == RPC_METHOD_NOT_FOUND)55nStatus = HTTP_NOT_FOUND;57return JSONRPCReplyObj(NullUniValue, std::move(objError), jreq.id, jreq.m_json_version);58}60//This function checks username and password against -rpcauth61//entries from config file.62static bool CheckUserAuthorized(std::string_view user, std::string_view pass)63{64for (const auto& fields : g_rpcauth) {65if (!TimingResistantEqual(std::string_view(fields[0]), user)) {66continue;67}69const std::string& salt = fields[1];70const std::string& hash = fields[2];72std::array<unsigned char, CHMAC_SHA256::OUTPUT_SIZE> out;73CHMAC_SHA256(UCharCast(salt.data()), salt.size()).Write(UCharCast(pass.data()), pass.size()).Finalize(out.data());74std::string hash_from_pass = HexStr(out);76if (TimingResistantEqual(hash_from_pass, hash)) {77return true;78}79}80return false;81}83static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUsernameOut)84{85if (!strAuth.starts_with("Basic "))86return false;87std::string_view strUserPass64 = TrimStringView(std::string_view{strAuth}.substr(6));88auto userpass_data = DecodeBase64(strUserPass64);89std::string strUserPass;90if (!userpass_data) return false;91strUserPass.assign(userpass_data->begin(), userpass_data->end());93size_t colon_pos = strUserPass.find(':');94if (colon_pos == std::string::npos) {95return false; // Invalid basic auth.96}97std::string user = strUserPass.substr(0, colon_pos);98std::string pass = strUserPass.substr(colon_pos + 1);99strAuthUsernameOut = user;100return CheckUserAuthorized(user, pass);101}103UniValue ExecuteHTTPRPC(const UniValue& valRequest, JSONRPCRequest& jreq, HTTPStatusCode& status)104{105status = HTTP_OK;106try {107bool user_has_whitelist = g_rpc_whitelist.contains(jreq.authUser);108if (!user_has_whitelist && g_rpc_whitelist_default) {109LogWarning("RPC User %s not allowed to call any methods", jreq.authUser);110status = HTTP_FORBIDDEN;111return {};113// singleton request114} else if (valRequest.isObject()) {115jreq.parse(valRequest);116if (user_has_whitelist && !g_rpc_whitelist[jreq.authUser].contains(jreq.strMethod)) {117LogWarning("RPC User %s not allowed to call method %s", jreq.authUser, jreq.strMethod);118status = HTTP_FORBIDDEN;119return {};120}122// Legacy 1.0/1.1 behavior is for failed requests to throw123// exceptions which return HTTP errors and RPC errors to the client.124// 2.0 behavior is to catch exceptions and return HTTP success with125// RPC errors, as long as there is not an actual HTTP server error.126const bool catch_errors{jreq.m_json_version == JSONRPCVersion::V2};127UniValue reply{JSONRPCExec(jreq, catch_errors)};128if (jreq.IsNotification()) {129// Even though we do execute notifications, we do not respond to them130status = HTTP_NO_CONTENT;131return {};132}133return reply;134// array of requests135} else if (valRequest.isArray()) {136// Check authorization for each request's method137if (user_has_whitelist) {138for (unsigned int reqIdx = 0; reqIdx < valRequest.size(); reqIdx++) {139if (!valRequest[reqIdx].isObject()) {140throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object");141} else {142const UniValue& request = valRequest[reqIdx].get_obj();143// Parse method144std::string strMethod = request.find_value("method").get_str();145if (!g_rpc_whitelist[jreq.authUser].contains(strMethod)) {146LogWarning("RPC User %s not allowed to call method %s", jreq.authUser, strMethod);147status = HTTP_FORBIDDEN;148return {};149}150}151}152}154// Execute each request155UniValue reply = UniValue::VARR;156for (size_t i{0}; i < valRequest.size(); ++i) {157// Batches never throw HTTP errors, they are always just included158// in "HTTP OK" responses. Notifications never get any response.159UniValue response;160try {161jreq.parse(valRequest[i]);162response = JSONRPCExec(jreq, /*catch_errors=*/true);163} catch (UniValue& e) {164response = JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version);165} catch (const std::exception& e) {166response = JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id, jreq.m_json_version);167}168if (!jreq.IsNotification()) {169reply.push_back(std::move(response));170}171}172// Return no response for an all-notification batch, but only if the173// batch request is non-empty. Technically according to the JSON-RPC174// 2.0 spec, an empty batch request should also return no response,175// However, if the batch request is empty, it means the request did176// not contain any JSON-RPC version numbers, so returning an empty177// response could break backwards compatibility with old RPC clients178// relying on previous behavior. Return an empty array instead of an179// empty response in this case to favor being backwards compatible180// over complying with the JSON-RPC 2.0 spec in this case.181if (reply.size() == 0 && valRequest.size() > 0) {182status = HTTP_NO_CONTENT;183return {};184}185return reply;186}187else188throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");189} catch (UniValue& e) {190return JSONErrorReply(std::move(e), jreq, status);191} catch (const std::exception& e) {192return JSONErrorReply(JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq, status);193}194}196static void HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)197{198// JSONRPC handles only POST199if (req->GetRequestMethod() != HTTPRequestMethod::POST) {200req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests");201return;202}203// Check authorization204std::optional<std::string> auth_header = req->GetHeader("authorization");205if (!auth_header) {206req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);207req->WriteReply(HTTP_UNAUTHORIZED);208return;209}211JSONRPCRequest jreq;212jreq.context = context;213jreq.peerAddr = req->GetPeer().ToStringAddrPort();214jreq.URI = req->GetURI();215if (!RPCAuthorized(*auth_header, jreq.authUser)) {216LogWarning("ThreadRPCServer incorrect password attempt from %s", jreq.peerAddr);218/* Deter brute-forcing219If this results in a DoS the user really220shouldn't have their RPC port exposed. */221UninterruptibleSleep(std::chrono::milliseconds{250});223req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);224req->WriteReply(HTTP_UNAUTHORIZED);225return;226}228// Generate reply229HTTPStatusCode status;230UniValue reply;231UniValue request;232if (request.read(req->ReadBody())) {233reply = ExecuteHTTPRPC(request, jreq, status);234} else {235reply = JSONErrorReply(JSONRPCError(RPC_PARSE_ERROR, "Parse error"), jreq, status);236}238// Write reply239if (reply.isNull()) {240// Error case or no-content notification reply.241req->WriteReply(status);242} else {243req->WriteHeader("Content-Type", "application/json");244req->WriteReply(status, reply.write() + "\n");245}246}248static bool InitRPCAuthentication()249{250std::string user;
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.