// Copyright (c) 2016-2024 The Hush developers // Distributed under the GPLv3 software license, see the accompanying // file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html /****************************************************************************** * Copyright © 2014-2019 The SuperNET Developers. * * * * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * * the top-level directory of this distribution for the individual copyright * * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * * SuperNET software, including this file may be copied, modified, propagated * * or distributed except according to the terms contained in the LICENSE file * * * * Removal or modification of this copyright notice is prohibited. * * * ******************************************************************************/ #include "httprpc.h" #include "chainparams.h" #include "httpserver.h" #include "key_io.h" #include "rpc/protocol.h" #include "rpc/server.h" #include "random.h" #include "sync.h" #include "util.h" #include "util/strencodings.h" #include "ui_interface.h" #include // 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, boost::function& 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() { return "HTTP"; } RPCTimerBase* NewTimer(boost::function& func, int64_t millis) { 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); } static bool RPCAuthorized(const std::string& strAuth) { 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); return TimingResistantEqual(strUserPass, strRPCUserColonPass); } 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 authHeader = req->GetHeader("authorization"); if (!authHeader.first) { req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); req->WriteReply(HTTP_UNAUTHORIZED); return false; } if (!RPCAuthorized(authHeader.second)) { 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; } JSONRequest jreq; try { // Parse request UniValue valRequest; if (!valRequest.read(req->ReadBody())) throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); std::string strReply; // singleton request if (valRequest.isObject()) { jreq.parse(valRequest); if (!RPCAuthorized(authHeader.second)) { LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", req->GetPeer().ToString()); MilliSleep(250); req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); req->WriteReply(HTTP_UNAUTHORIZED); return false; } UniValue result = tableRPC.execute(jreq.strMethod, jreq.params); // Send reply strReply = JSONRPCReply(result, NullUniValue, jreq.id); // array of requests } else if (valRequest.isArray()) strReply = JSONRPCExecBatch(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 (mapArgs["-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 { strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; } return true; } bool StartHTTPRPC() { LogPrint("rpc", "Starting HTTP RPC server\n"); if (!InitRPCAuthentication()) return false; RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); assert(EventBase()); httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase()); RPCRegisterTimerInterface(httpRPCTimerInterface); return true; } void InterruptHTTPRPC() { LogPrint("rpc", "Interrupting HTTP RPC server\n"); } void StopHTTPRPC() { LogPrint("rpc", "Stopping HTTP RPC server\n"); UnregisterHTTPHandler("/", true); if (httpRPCTimerInterface) { RPCUnregisterTimerInterface(httpRPCTimerInterface); delete httpRPCTimerInterface; httpRPCTimerInterface = 0; } }