httprpc.cpp raw

   1  // Copyright (c) 2015-2022 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <httprpc.h>
   6  
   7  #include <common/args.h>
   8  #include <crypto/hmac_sha256.h>
   9  #include <httpserver.h>
  10  #include <logging.h>
  11  #include <netaddress.h>
  12  #include <rpc/protocol.h>
  13  #include <rpc/server.h>
  14  #include <util/fs.h>
  15  #include <util/fs_helpers.h>
  16  #include <util/strencodings.h>
  17  #include <util/string.h>
  18  #include <walletinitinterface.h>
  19  
  20  #include <algorithm>
  21  #include <atomic>
  22  #include <chrono>
  23  #include <fstream>
  24  #include <iterator>
  25  #include <map>
  26  #include <memory>
  27  #include <optional>
  28  #include <set>
  29  #include <string>
  30  #include <utility>
  31  #include <vector>
  32  
  33  using util::SplitString;
  34  using util::TrimStringView;
  35  
  36  /** WWW-Authenticate to present with 401 Unauthorized response */
  37  static const char* WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\"";
  38  
  39  /** Simple one-shot callback timer to be used by the RPC mechanism to e.g.
  40   * re-lock the wallet.
  41   */
  42  class HTTPRPCTimer : public RPCTimerBase
  43  {
  44  public:
  45      HTTPRPCTimer(struct event_base* eventBase, std::function<void()>& func, int64_t millis) :
  46          ev(eventBase, false, func)
  47      {
  48          struct timeval tv;
  49          tv.tv_sec = millis/1000;
  50          tv.tv_usec = (millis%1000)*1000;
  51          ev.trigger(&tv);
  52      }
  53  private:
  54      HTTPEvent ev;
  55  };
  56  
  57  class HTTPRPCTimerInterface : public RPCTimerInterface
  58  {
  59  public:
  60      explicit HTTPRPCTimerInterface(struct event_base* _base) : base(_base)
  61      {
  62      }
  63      const char* Name() override
  64      {
  65          return "HTTP";
  66      }
  67      RPCTimerBase* NewTimer(std::function<void()>& func, int64_t millis) override
  68      {
  69          return new HTTPRPCTimer(base, func, millis);
  70      }
  71  private:
  72      struct event_base* base;
  73  };
  74  
  75  
  76  /* Stored RPC timer interface (for unregistration) */
  77  static std::unique_ptr<HTTPRPCTimerInterface> httpRPCTimerInterface;
  78  /* List of -rpcauth values */
  79  static std::vector<std::vector<std::string>> g_rpcauth;
  80  /* RPC Auth Whitelist */
  81  static std::map<std::string, std::set<std::string>> g_rpc_whitelist;
  82  static bool g_rpc_whitelist_default = false;
  83  
  84  static void JSONErrorReply(HTTPRequest* req, UniValue objError, const JSONRPCRequest& jreq)
  85  {
  86      // Sending HTTP errors is a legacy JSON-RPC behavior.
  87      Assume(jreq.m_json_version != JSONRPCVersion::V2);
  88  
  89      // Send error reply from json-rpc error object
  90      int nStatus = HTTP_INTERNAL_SERVER_ERROR;
  91      int code = objError.find_value("code").getInt<int>();
  92  
  93      if (code == RPC_INVALID_REQUEST)
  94          nStatus = HTTP_BAD_REQUEST;
  95      else if (code == RPC_METHOD_NOT_FOUND)
  96          nStatus = HTTP_NOT_FOUND;
  97  
  98      std::string strReply = JSONRPCReplyObj(NullUniValue, std::move(objError), jreq.id, jreq.m_json_version).write() + "\n";
  99  
 100      req->WriteHeader("Content-Type", "application/json");
 101      req->WriteReply(nStatus, strReply);
 102  }
 103  
 104  //This function checks username and password against -rpcauth
 105  //entries from config file.
 106  static bool multiUserAuthorized(std::string strUserPass, std::string& out_wallet_restriction)
 107  {
 108      if (strUserPass.find(':') == std::string::npos) {
 109          return false;
 110      }
 111      std::string strUser = strUserPass.substr(0, strUserPass.find(':'));
 112      std::string strPass = strUserPass.substr(strUserPass.find(':') + 1);
 113  
 114      for (const auto& vFields : g_rpcauth) {
 115          std::string strName = vFields[0];
 116          if (!TimingResistantEqual(strName, strUser)) {
 117              continue;
 118          }
 119  
 120          std::string strSalt = vFields[1];
 121          std::string strHash = vFields[2];
 122  
 123          static const unsigned int KEY_SIZE = 32;
 124          unsigned char out[KEY_SIZE];
 125  
 126          CHMAC_SHA256(reinterpret_cast<const unsigned char*>(strSalt.data()), strSalt.size()).Write(reinterpret_cast<const unsigned char*>(strPass.data()), strPass.size()).Finalize(out);
 127          std::vector<unsigned char> hexvec(out, out+KEY_SIZE);
 128          std::string strHashFromPass = HexStr(hexvec);
 129  
 130          if (TimingResistantEqual(strHashFromPass, strHash)) {
 131              out_wallet_restriction = (vFields.size() > 3) ? vFields[3] : "";
 132              return true;
 133          }
 134      }
 135      return false;
 136  }
 137  
 138  static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUsernameOut, std::string& out_wallet_restriction)
 139  {
 140      if (strAuth.substr(0, 6) != "Basic ")
 141          return false;
 142      std::string_view strUserPass64 = TrimStringView(std::string_view{strAuth}.substr(6));
 143      auto userpass_data = DecodeBase64(strUserPass64);
 144      std::string strUserPass;
 145      if (!userpass_data) return false;
 146      strUserPass.assign(userpass_data->begin(), userpass_data->end());
 147  
 148      if (strUserPass.find(':') != std::string::npos)
 149          strAuthUsernameOut = strUserPass.substr(0, strUserPass.find(':'));
 150  
 151      return multiUserAuthorized(strUserPass, out_wallet_restriction);
 152  }
 153  
 154  static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
 155  {
 156      // JSONRPC handles only POST
 157      if (req->GetRequestMethod() != HTTPRequest::POST) {
 158          req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests");
 159          return false;
 160      }
 161      // Check authorization
 162      std::pair<bool, std::string> authHeader = req->GetHeader("authorization");
 163      if (!authHeader.first) {
 164          req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
 165          req->WriteReply(HTTP_UNAUTHORIZED);
 166          return false;
 167      }
 168  
 169      JSONRPCRequest jreq;
 170      jreq.context = context;
 171      jreq.peerAddr = req->GetPeer().ToStringAddrPort();
 172  
 173      // Exponential backoff for failed authentication attempts (shared across all worker threads)
 174      static std::atomic<std::chrono::milliseconds::rep> auth_delay_ms{250};
 175  
 176      if (!RPCAuthorized(authHeader.second, jreq.authUser, jreq.m_wallet_restriction)) {
 177          LogWarning("ThreadRPCServer incorrect password attempt from %s", jreq.peerAddr);
 178  
 179          auto delay = std::chrono::milliseconds{auth_delay_ms.load(std::memory_order_relaxed)};
 180          UninterruptibleSleep(delay);
 181          auth_delay_ms.store(std::min<std::chrono::milliseconds::rep>(
 182              delay.count() * 2, 8000), std::memory_order_relaxed);
 183  
 184          req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
 185          req->WriteReply(HTTP_UNAUTHORIZED);
 186          return false;
 187      }
 188  
 189      // Reset backoff on successful authentication
 190      auth_delay_ms.store(250, std::memory_order_relaxed);
 191  
 192      try {
 193          // Parse request
 194          UniValue valRequest;
 195          if (!valRequest.read(req->ReadBody()))
 196              throw JSONRPCError(RPC_PARSE_ERROR, "Parse error");
 197  
 198          // Set the URI
 199          jreq.URI = req->GetURI();
 200  
 201          UniValue reply;
 202          bool user_has_whitelist = g_rpc_whitelist.count(jreq.authUser);
 203          if (!user_has_whitelist && g_rpc_whitelist_default) {
 204              LogWarning("RPC User %s not allowed to call any methods", jreq.authUser);
 205              req->WriteReply(HTTP_FORBIDDEN);
 206              return false;
 207  
 208          // singleton request
 209          } else if (valRequest.isObject()) {
 210              jreq.parse(valRequest);
 211              if (user_has_whitelist && !g_rpc_whitelist[jreq.authUser].count(jreq.strMethod)) {
 212                  LogWarning("RPC User %s not allowed to call method %s", jreq.authUser, jreq.strMethod);
 213                  req->WriteReply(HTTP_FORBIDDEN);
 214                  return false;
 215              }
 216  
 217              // Legacy 1.0/1.1 behavior is for failed requests to throw
 218              // exceptions which return HTTP errors and RPC errors to the client.
 219              // 2.0 behavior is to catch exceptions and return HTTP success with
 220              // RPC errors, as long as there is not an actual HTTP server error.
 221              const bool catch_errors{jreq.m_json_version == JSONRPCVersion::V2};
 222              reply = JSONRPCExec(jreq, catch_errors);
 223  
 224              if (jreq.IsNotification()) {
 225                  // Even though we do execute notifications, we do not respond to them
 226                  req->WriteReply(HTTP_NO_CONTENT);
 227                  return true;
 228              }
 229  
 230          // array of requests
 231          } else if (valRequest.isArray()) {
 232              // Check authorization for each request's method
 233              if (user_has_whitelist) {
 234                  for (unsigned int reqIdx = 0; reqIdx < valRequest.size(); reqIdx++) {
 235                      if (!valRequest[reqIdx].isObject()) {
 236                          throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object");
 237                      } else {
 238                          const UniValue& request = valRequest[reqIdx].get_obj();
 239                          // Parse method
 240                          std::string strMethod = request.find_value("method").get_str();
 241                          if (!g_rpc_whitelist[jreq.authUser].count(strMethod)) {
 242                              LogWarning("RPC User %s not allowed to call method %s", jreq.authUser, strMethod);
 243                              req->WriteReply(HTTP_FORBIDDEN);
 244                              return false;
 245                          }
 246                      }
 247                  }
 248              }
 249  
 250              // Execute each request
 251              static constexpr size_t MAX_BATCH_SIZE = 100;
 252              if (valRequest.size() > MAX_BATCH_SIZE) {
 253                  req->WriteReply(HTTP_BAD_REQUEST, "Batch request exceeds maximum size");
 254                  return false;
 255              }
 256              reply = UniValue::VARR;
 257              for (size_t i{0}; i < valRequest.size(); ++i) {
 258                  // Batches never throw HTTP errors, they are always just included
 259                  // in "HTTP OK" responses. Notifications never get any response.
 260                  UniValue response;
 261                  try {
 262                      jreq.parse(valRequest[i]);
 263                      response = JSONRPCExec(jreq, /*catch_errors=*/true);
 264                  } catch (UniValue& e) {
 265                      response = JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version);
 266                  } catch (const std::exception& e) {
 267                      response = JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id, jreq.m_json_version);
 268                  }
 269                  if (!jreq.IsNotification()) {
 270                      reply.push_back(std::move(response));
 271                  }
 272              }
 273              // Return no response for an all-notification batch, but only if the
 274              // batch request is non-empty. Technically according to the JSON-RPC
 275              // 2.0 spec, an empty batch request should also return no response,
 276              // However, if the batch request is empty, it means the request did
 277              // not contain any JSON-RPC version numbers, so returning an empty
 278              // response could break backwards compatibility with old RPC clients
 279              // relying on previous behavior. Return an empty array instead of an
 280              // empty response in this case to favor being backwards compatible
 281              // over complying with the JSON-RPC 2.0 spec in this case.
 282              if (reply.size() == 0 && valRequest.size() > 0) {
 283                  req->WriteReply(HTTP_NO_CONTENT);
 284                  return true;
 285              }
 286          }
 287          else
 288              throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");
 289  
 290          req->WriteHeader("Content-Type", "application/json");
 291          req->WriteReply(HTTP_OK, reply.write() + "\n");
 292      } catch (UniValue& e) {
 293          JSONErrorReply(req, std::move(e), jreq);
 294          return false;
 295      } catch (const std::exception& e) {
 296          JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq);
 297          return false;
 298      }
 299      return true;
 300  }
 301  
 302  static bool InitRPCAuthentication()
 303  {
 304      std::string strRPCUserColonPass;
 305  
 306      if (gArgs.GetArg("-rpcpassword", "") == "")
 307      {
 308          std::optional<fs::perms> cookie_perms{std::nullopt};
 309          auto cookie_perms_arg{gArgs.GetArg("-rpccookieperms")};
 310          if (cookie_perms_arg) {
 311              if (*cookie_perms_arg == "0") {
 312                  cookie_perms = std::nullopt;
 313              } else if (cookie_perms_arg->empty() || *cookie_perms_arg == "1") {
 314                  // leave at default
 315              } else {
 316                  auto perm_opt = InterpretPermString(*cookie_perms_arg);
 317                  if (!perm_opt) {
 318                      LogError("Invalid -rpccookieperms=%s; must be one of 'owner', 'group', or 'all'.", *cookie_perms_arg);
 319                      return false;
 320                  }
 321                  cookie_perms = *perm_opt;
 322              }
 323          }
 324  
 325          if (!GenerateAuthCookie(&strRPCUserColonPass, std::make_pair(cookie_perms, bool(cookie_perms_arg)))) {
 326              return false;
 327          }
 328          if (strRPCUserColonPass.empty()) {
 329              LogInfo("RPC authentication cookie file generation is disabled.");
 330          } else {
 331              LogInfo("Using random cookie authentication.");
 332          }
 333      } else {
 334          LogInfo("Using rpcuser/rpcpassword authentication.");
 335          LogWarning("The use of rpcuser/rpcpassword is less secure, because credentials are configured in plain text. It is recommended that locally-run instances switch to cookie-based auth, or otherwise to use hashed rpcauth credentials. See share/rpcauth in the source directory for more information.");
 336          strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
 337      }
 338  
 339      // If there is a plaintext credential, hash it with a random salt before storage.
 340      if (!strRPCUserColonPass.empty()) {
 341          std::vector<std::string> fields{SplitString(strRPCUserColonPass, ':')};
 342          if (fields.size() != 2) {
 343              LogError("Unable to parse RPC credentials. The configured rpcuser or rpcpassword cannot contain a \":\".");
 344              return false;
 345          }
 346          const std::string& user = fields[0];
 347          const std::string& pass = fields[1];
 348  
 349          // Generate a random 16 byte hex salt.
 350          std::array<unsigned char, 16> raw_salt;
 351          GetStrongRandBytes(raw_salt);
 352          std::string salt = HexStr(raw_salt);
 353  
 354          // Compute HMAC.
 355          std::array<unsigned char, CHMAC_SHA256::OUTPUT_SIZE> out;
 356          CHMAC_SHA256(UCharCast(salt.data()), salt.size()).Write(UCharCast(pass.data()), pass.size()).Finalize(out.data());
 357          std::string hash = HexStr(out);
 358  
 359          g_rpcauth.push_back({user, salt, hash});
 360      }
 361  
 362      constexpr auto AddRPCAuth = [](const std::string& rpcauth) {
 363          std::vector<std::string> fields{SplitString(rpcauth, ':')};
 364          if (fields.size() < 2 || fields.size() > 3) {
 365              return false;
 366          }
 367          const std::vector<std::string> salt_hmac{SplitString(fields[1], '$')};
 368          if (salt_hmac.size() == 2) {
 369              fields.erase(fields.begin() + 1);
 370              fields.insert(fields.begin() + 1, salt_hmac.begin(), salt_hmac.end());
 371              g_rpcauth.push_back(fields);
 372          } else {
 373              return false;
 374          }
 375          return true;
 376      };
 377      if (!(gArgs.IsArgNegated("-rpcauth") || (gArgs.GetArgs("-rpcauth").empty() && gArgs.GetArgs("-rpcauthfile").empty()))) {
 378          LogInfo("Using rpcauth authentication.\n");
 379          for (const std::string& rpcauth : gArgs.GetArgs("-rpcauth")) {
 380              if (rpcauth.empty()) continue;
 381              if (!AddRPCAuth(rpcauth)) {
 382                  LogWarning("Invalid -rpcauth argument.");
 383                  return false;
 384              }
 385          }
 386          for (const std::string& path : gArgs.GetArgs("-rpcauthfile")) {
 387              std::ifstream file;
 388              file.open(path);
 389              if (!file.is_open()) continue;
 390              std::string rpcauth;
 391              size_t lineno = 0;
 392              while (std::getline(file, rpcauth)) {
 393                  ++lineno;
 394                  if (!AddRPCAuth(rpcauth)) {
 395                      LogPrintf("WARNING: Invalid line %s in -rpcauthfile=%s; ignoring\n", lineno, path);
 396                  }
 397              }
 398          }
 399      }
 400  
 401      g_rpc_whitelist_default = gArgs.GetBoolArg("-rpcwhitelistdefault", !gArgs.GetArgs("-rpcwhitelist").empty());
 402      for (const std::string& strRPCWhitelist : gArgs.GetArgs("-rpcwhitelist")) {
 403          auto pos = strRPCWhitelist.find(':');
 404          std::string strUser = strRPCWhitelist.substr(0, pos);
 405          bool intersect = g_rpc_whitelist.count(strUser);
 406          std::set<std::string>& whitelist = g_rpc_whitelist[strUser];
 407          if (pos != std::string::npos) {
 408              std::string strWhitelist = strRPCWhitelist.substr(pos + 1);
 409              std::vector<std::string> whitelist_split = SplitString(strWhitelist, ", ");
 410              std::set<std::string> new_whitelist{
 411                  std::make_move_iterator(whitelist_split.begin()),
 412                  std::make_move_iterator(whitelist_split.end())};
 413              if (intersect) {
 414                  std::set<std::string> tmp_whitelist;
 415                  std::set_intersection(new_whitelist.begin(), new_whitelist.end(),
 416                         whitelist.begin(), whitelist.end(), std::inserter(tmp_whitelist, tmp_whitelist.end()));
 417                  new_whitelist = std::move(tmp_whitelist);
 418              }
 419              whitelist = std::move(new_whitelist);
 420          }
 421      }
 422  
 423      return true;
 424  }
 425  
 426  bool StartHTTPRPC(const std::any& context)
 427  {
 428      LogDebug(BCLog::RPC, "Starting HTTP RPC server\n");
 429      if (!InitRPCAuthentication())
 430          return false;
 431  
 432      auto handle_rpc = [context](HTTPRequest* req, const std::string&) { return HTTPReq_JSONRPC(context, req); };
 433      RegisterHTTPHandler("/", true, handle_rpc);
 434      if (g_wallet_init_interface.HasWalletSupport()) {
 435          RegisterHTTPHandler("/wallet/", false, handle_rpc);
 436      }
 437      struct event_base* eventBase = EventBase();
 438      assert(eventBase);
 439      httpRPCTimerInterface = std::make_unique<HTTPRPCTimerInterface>(eventBase);
 440      RPCSetTimerInterface(httpRPCTimerInterface.get());
 441      return true;
 442  }
 443  
 444  void InterruptHTTPRPC()
 445  {
 446      LogDebug(BCLog::RPC, "Interrupting HTTP RPC server\n");
 447  }
 448  
 449  void StopHTTPRPC()
 450  {
 451      LogDebug(BCLog::RPC, "Stopping HTTP RPC server\n");
 452      UnregisterHTTPHandler("/", true);
 453      if (g_wallet_init_interface.HasWalletSupport()) {
 454          UnregisterHTTPHandler("/wallet/", false);
 455      }
 456      if (httpRPCTimerInterface) {
 457          RPCUnsetTimerInterface(httpRPCTimerInterface.get());
 458          httpRPCTimerInterface.reset();
 459      }
 460  }
 461  
 462  std::set<std::string> GetWhitelistedRpcs(const std::string& user_name)
 463  {
 464      if (auto it = g_rpc_whitelist.find(user_name); it != g_rpc_whitelist.end()) {
 465          return it->second;
 466      }
 467      if (g_rpc_whitelist_default) {
 468          return std::set<std::string>();
 469      }
 470  
 471      // Build a list of every method
 472      std::set<std::string> allowed_methods;
 473      for (const auto& method_name : tableRPC.listCommands()) {
 474          allowed_methods.insert(method_name);
 475      }
 476      return allowed_methods;
 477  }
 478