request.cpp raw

   1  // Copyright (c) 2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-2022 The Limenka developers
   3  // Distributed under the MIT software license, see the accompanying
   4  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  
   6  #include <rpc/request.h>
   7  
   8  #include <common/args.h>
   9  #include <logging.h>
  10  #include <random.h>
  11  #include <rpc/protocol.h>
  12  #include <util/fs.h>
  13  #include <util/fs_helpers.h>
  14  #include <util/strencodings.h>
  15  
  16  #include <fstream>
  17  #include <stdexcept>
  18  #include <string>
  19  #include <utility>
  20  #include <vector>
  21  
  22  /**
  23   * JSON-RPC protocol.  Limenka speaks version 1.0 for maximum compatibility,
  24   * but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
  25   * unspecified (HTTP errors and contents of 'error').
  26   *
  27   * 1.0 spec: http://json-rpc.org/wiki/specification
  28   * 1.2 spec: http://jsonrpc.org/historical/json-rpc-over-http.html
  29   *
  30   * If the server receives a request with the JSON-RPC 2.0 marker `{"jsonrpc": "2.0"}`
  31   * then Limenka will respond with a strictly specified response.
  32   * It will only return an HTTP error code if an actual HTTP error is encountered
  33   * such as the endpoint is not found (404) or the request is not formatted correctly (500).
  34   * Otherwise the HTTP code is always OK (200) and RPC errors will be included in the
  35   * response body.
  36   *
  37   * 2.0 spec: https://www.jsonrpc.org/specification
  38   *
  39   * Also see http://www.simple-is-better.org/rpc/#differences-between-1-0-and-2-0
  40   */
  41  
  42  UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id)
  43  {
  44      UniValue request(UniValue::VOBJ);
  45      request.pushKV("method", strMethod);
  46      request.pushKV("params", params);
  47      request.pushKV("id", id);
  48      request.pushKV("jsonrpc", "2.0");
  49      return request;
  50  }
  51  
  52  UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional<UniValue> id, JSONRPCVersion jsonrpc_version)
  53  {
  54      UniValue reply(UniValue::VOBJ);
  55      // Add JSON-RPC version number field in v2 only.
  56      if (jsonrpc_version == JSONRPCVersion::V2) reply.pushKV("jsonrpc", "2.0");
  57  
  58      // Add both result and error fields in v1, even though one will be null.
  59      // Omit the null field in v2.
  60      if (error.isNull()) {
  61          reply.pushKV("result", std::move(result));
  62          if (jsonrpc_version == JSONRPCVersion::V1_LEGACY) reply.pushKV("error", NullUniValue);
  63      } else {
  64          if (jsonrpc_version == JSONRPCVersion::V1_LEGACY) reply.pushKV("result", NullUniValue);
  65          reply.pushKV("error", std::move(error));
  66      }
  67      if (id.has_value()) reply.pushKV("id", std::move(id.value()));
  68      return reply;
  69  }
  70  
  71  UniValue JSONRPCError(int code, const std::string& message)
  72  {
  73      UniValue error(UniValue::VOBJ);
  74      error.pushKV("code", code);
  75      error.pushKV("message", message);
  76      return error;
  77  }
  78  
  79  /** Username used when cookie authentication is in use (arbitrary, only for
  80   * recognizability in debugging/logging purposes)
  81   */
  82  static const std::string COOKIEAUTH_USER = "__cookie__";
  83  /** Default name for auth cookie file */
  84  static const char* const COOKIEAUTH_FILE = ".cookie";
  85  
  86  /** Get name of RPC authentication cookie file */
  87  static fs::path GetAuthCookieFile(bool temp=false)
  88  {
  89      fs::path arg = gArgs.GetPathArg("-rpccookiefile", COOKIEAUTH_FILE);
  90      if (arg.empty()) {
  91          return {}; // -norpccookiefile was specified
  92      }
  93      if (temp) {
  94          arg += ".tmp";
  95      }
  96      return AbsPathForConfigVal(gArgs, arg);
  97  }
  98  
  99  static std::optional<std::string> g_generated_cookie;
 100  
 101  bool GenerateAuthCookie(std::string* cookie_out, const std::pair<std::optional<fs::perms>, bool>& cookie_perms)
 102  {
 103      const size_t COOKIE_SIZE = 32;
 104      unsigned char rand_pwd[COOKIE_SIZE];
 105      GetRandBytes(rand_pwd);
 106      std::string cookie = COOKIEAUTH_USER + ":" + HexStr(rand_pwd);
 107  
 108      /** the umask determines what permissions are used to create this file -
 109       * these are set to 0077 in common/system.cpp.
 110       */
 111      std::ofstream file;
 112      fs::path filepath_tmp = GetAuthCookieFile(true);
 113      if (filepath_tmp.empty()) {
 114          return true; // -norpccookiefile
 115      }
 116      try {
 117          fs::remove(filepath_tmp);
 118      } catch (const fs::filesystem_error&) {
 119          // ignore
 120      }
 121      file.open(filepath_tmp);
 122      if (!file.is_open()) {
 123          LogWarning("Unable to open cookie authentication file %s for writing", fs::PathToString(filepath_tmp));
 124          return false;
 125      }
 126  
 127      if (cookie_perms.first) {
 128          std::error_code code;
 129          fs::permissions(filepath_tmp, cookie_perms.first.value(), fs::perm_options::replace, code);
 130          if (code) {
 131              LogWarning("Unable to set permissions on cookie authentication file %s", fs::PathToString(filepath_tmp));
 132              return false;
 133          }
 134      }
 135  
 136      file << cookie;
 137      file.close();
 138  
 139      fs::path filepath = GetAuthCookieFile(false);
 140      try {
 141          fs::remove(filepath);
 142      } catch (const fs::filesystem_error&) {
 143          // ignore
 144      }
 145      if (!RenameOver(filepath_tmp, filepath)) {
 146          LogWarning("Unable to rename cookie authentication file %s to %s", fs::PathToString(filepath_tmp), fs::PathToString(filepath));
 147          return false;
 148      }
 149  
 150      g_generated_cookie = cookie;
 151      LogInfo("Generated RPC authentication cookie %s\n", fs::PathToString(filepath));
 152      LogInfo("Permissions used for cookie%s: %s\n",
 153                (cookie_perms.first && cookie_perms.second) ? " (set by -rpccookieperms)" : "",
 154                PermsToSymbolicString(fs::status(filepath).permissions()));
 155  
 156      if (cookie_out)
 157          *cookie_out = cookie;
 158      return true;
 159  }
 160  
 161  bool GetAuthCookie(std::string *cookie_out)
 162  {
 163      std::ifstream file;
 164      std::string cookie;
 165      fs::path filepath = GetAuthCookieFile();
 166      if (filepath.empty()) {
 167          return true; // -norpccookiefile
 168      }
 169      file.open(filepath);
 170      if (!file.is_open())
 171          return false;
 172      std::getline(file, cookie);
 173      file.close();
 174  
 175      if (cookie_out)
 176          *cookie_out = cookie;
 177      return true;
 178  }
 179  
 180  void DeleteAuthCookie()
 181  {
 182      try {
 183          std::string existing_cookie;
 184          if (GetAuthCookie(&existing_cookie) && g_generated_cookie == existing_cookie) {
 185              // Delete the cookie file if it exists and was generated by this process
 186              fs::remove(GetAuthCookieFile());
 187          }
 188      } catch (const fs::filesystem_error& e) {
 189          LogPrintf("%s: Unable to remove random auth cookie file %s: %s\n", __func__, fs::PathToString(e.path1()), fsbridge::get_filesystem_error_message(e));
 190      }
 191  }
 192  
 193  std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in)
 194  {
 195      if (!in.isArray()) {
 196          throw std::runtime_error("Batch must be an array");
 197      }
 198      const size_t num {in.size()};
 199      std::vector<UniValue> batch(num);
 200      for (const UniValue& rec : in.getValues()) {
 201          if (!rec.isObject()) {
 202              throw std::runtime_error("Batch member must be an object");
 203          }
 204          size_t id = rec["id"].getInt<int>();
 205          if (id >= num) {
 206              throw std::runtime_error("Batch member id is larger than batch size");
 207          }
 208          batch[id] = rec;
 209      }
 210      return batch;
 211  }
 212  
 213  void JSONRPCRequest::parse(const UniValue& valRequest)
 214  {
 215      // Parse request
 216      if (!valRequest.isObject())
 217          throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object");
 218      const UniValue& request = valRequest.get_obj();
 219  
 220      // Parse id now so errors from here on will have the id
 221      if (request.exists("id")) {
 222          id = request.find_value("id");
 223      } else {
 224          id = std::nullopt;
 225      }
 226  
 227      // Check for JSON-RPC 2.0 (default 1.1)
 228      m_json_version = JSONRPCVersion::V1_LEGACY;
 229      const UniValue& jsonrpc_version = request.find_value("jsonrpc");
 230      if (!jsonrpc_version.isNull()) {
 231          if (jsonrpc_version.isStr() && jsonrpc_version.get_str() == "2.0") {
 232              m_json_version = JSONRPCVersion::V2;
 233          }
 234      }
 235  
 236      // Parse method
 237      const UniValue& valMethod{request.find_value("method")};
 238      if (valMethod.isNull())
 239          throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method");
 240      if (!valMethod.isStr())
 241          throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string");
 242      strMethod = valMethod.get_str();
 243      if (fLogIPs)
 244          LogDebug(BCLog::RPC, "ThreadRPCServer method=%s user=%s peeraddr=%s\n", SanitizeString(strMethod),
 245              this->authUser, this->peerAddr);
 246      else
 247          LogDebug(BCLog::RPC, "ThreadRPCServer method=%s user=%s\n", SanitizeString(strMethod), this->authUser);
 248  
 249      // Parse params
 250      const UniValue& valParams{request.find_value("params")};
 251      if (valParams.isArray() || valParams.isObject())
 252          params = valParams;
 253      else if (valParams.isNull())
 254          params = UniValue(UniValue::VARR);
 255      else
 256          throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object");
 257  }
 258