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