git / moonbite-core / src/httprpc.cpp · raw

// Copyright (c) 2015-2020 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 <chainparams.h>#include <crypto/hmac_sha256.h>#include <httpserver.h>#include <rpc/protocol.h>#include <rpc/server.h>#include <util/strencodings.h>#include <util/system.h>#include <util/translation.h>#include <walletinitinterface.h>#include <algorithm>#include <iterator>#include <map>#include <memory>#include <stdio.h>#include <set>#include <string>#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()>& 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()>& 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;/* RPC Auth Whitelist */static std::map<std::string, std::set<std::string>> g_rpc_whitelist;static bool g_rpc_whitelist_default = false;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.data()), strSalt.size()).Write(reinterpret_cast<const unsigned char*>(strPass.data()), 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(const util::Ref& context, HTTPRequest* req){    // 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(context);    jreq.peerAddr = req->GetPeer().ToString();    if (!RPCAuthorized(authHeader.second, jreq.authUser)) {        LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", jreq.peerAddr);        /* Deter brute-forcing           If this results in a DoS the user really           shouldn't have their RPC port exposed. */        UninterruptibleSleep(std::chrono::milliseconds{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;        bool user_has_whitelist = g_rpc_whitelist.count(jreq.authUser);        if (!user_has_whitelist && g_rpc_whitelist_default) {            LogPrintf("RPC User %s not allowed to call any methods\n", jreq.authUser);            req->WriteReply(HTTP_FORBIDDEN);            return false;        // singleton request        } else if (valRequest.isObject()) {            jreq.parse(valRequest);            if (user_has_whitelist && !g_rpc_whitelist[jreq.authUser].count(jreq.strMethod)) {                LogPrintf("RPC User %s not allowed to call method %s\n", jreq.authUser, jreq.strMethod);                req->WriteReply(HTTP_FORBIDDEN);                return false;            }            UniValue result = tableRPC.execute(jreq);            // Send reply            strReply = JSONRPCReply(result, NullUniValue, jreq.id);        // array of requests        } else if (valRequest.isArray()) {            if (user_has_whitelist) {                for (unsigned int reqIdx = 0; reqIdx < valRequest.size(); reqIdx++) {                    if (!valRequest[reqIdx].isObject()) {                        throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object");                    } else {                        const UniValue& request = valRequest[reqIdx].get_obj();                        // Parse method                        std::string strMethod = find_value(request, "method").get_str();                        if (!g_rpc_whitelist[jreq.authUser].count(strMethod)) {                            LogPrintf("RPC User %s not allowed to call method %s\n", jreq.authUser, strMethod);                            req->WriteReply(HTTP_FORBIDDEN);                            return false;                        }                    }                }            }            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("Using random cookie authentication.\n");        if (!GenerateAuthCookie(&strRPCUserColonPass)) {            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/rpcauth for rpcauth auth generation.\n");        strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");    }    if (gArgs.GetArg("-rpcauth","") != "")    {        LogPrintf("Using rpcauth authentication.\n");    }    g_rpc_whitelist_default = gArgs.GetBoolArg("-rpcwhitelistdefault", gArgs.IsArgSet("-rpcwhitelist"));    for (const std::string& strRPCWhitelist : gArgs.GetArgs("-rpcwhitelist")) {        auto pos = strRPCWhitelist.find(':');        std::string strUser = strRPCWhitelist.substr(0, pos);        bool intersect = g_rpc_whitelist.count(strUser);        std::set<std::string>& whitelist = g_rpc_whitelist[strUser];        if (pos != std::string::npos) {            std::string strWhitelist = strRPCWhitelist.substr(pos + 1);            std::set<std::string> new_whitelist;            boost::split(new_whitelist, strWhitelist, boost::is_any_of(", "));            if (intersect) {                std::set<std::string> tmp_whitelist;                std::set_intersection(new_whitelist.begin(), new_whitelist.end(),                       whitelist.begin(), whitelist.end(), std::inserter(tmp_whitelist, tmp_whitelist.end()));                new_whitelist = std::move(tmp_whitelist);            }            whitelist = std::move(new_whitelist);        }    }    return true;}bool StartHTTPRPC(const util::Ref& context){    LogPrint(BCLog::RPC, "Starting HTTP RPC server\n");    if (!InitRPCAuthentication())        return false;    auto handle_rpc = [&context](HTTPRequest* req, const std::string&) { return HTTPReq_JSONRPC(context, req); };    RegisterHTTPHandler("/", true, handle_rpc);    if (g_wallet_init_interface.HasWalletSupport()) {        RegisterHTTPHandler("/wallet/", false, handle_rpc);    }    struct event_base* eventBase = EventBase();    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 (g_wallet_init_interface.HasWalletSupport()) {        UnregisterHTTPHandler("/wallet/", false);    }    if (httpRPCTimerInterface) {        RPCUnsetTimerInterface(httpRPCTimerInterface.get());        httpRPCTimerInterface.reset();    }}