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 AuthCookieResult 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 AuthCookieResult::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 AuthCookieResult::Error; 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 AuthCookieResult::Error; 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 AuthCookieResult::Error; 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 AuthCookieResult::Ok; 146 } 147 148 AuthCookieResult GetAuthCookie(std::string& cookie_out) 149 { 150 std::ifstream file; 151 fs::path filepath = GetAuthCookieFile(); 152 if (filepath.empty()) { 153 return AuthCookieResult::Disabled; // -norpccookiefile 154 } 155 file.open(filepath.std_path()); 156 if (!file.is_open()) { 157 return AuthCookieResult::Error; 158 } 159 std::getline(file, cookie_out); 160 file.close(); 161 return AuthCookieResult::Ok; 162 } 163 164 void DeleteAuthCookie() 165 { 166 try { 167 if (g_generated_cookie) { 168 // Delete the cookie file if it was generated by this process 169 fs::remove(GetAuthCookieFile()); 170 } 171 } catch (const fs::filesystem_error& e) { 172 LogWarning("Unable to remove random auth cookie file %s: %s\n", fs::PathToString(e.path1()), e.code().message()); 173 } 174 } 175 176 std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in) 177 { 178 if (!in.isArray()) { 179 throw std::runtime_error("Batch must be an array"); 180 } 181 const size_t num {in.size()}; 182 std::vector<UniValue> batch(num); 183 for (const UniValue& rec : in.getValues()) { 184 if (!rec.isObject()) { 185 throw std::runtime_error("Batch member must be an object"); 186 } 187 size_t id = rec["id"].getInt<int>(); 188 if (id >= num) { 189 throw std::runtime_error("Batch member id is larger than batch size"); 190 } 191 batch[id] = rec; 192 } 193 return batch; 194 } 195 196 void JSONRPCRequest::parse(const UniValue& valRequest) 197 { 198 // Parse request 199 if (!valRequest.isObject()) 200 throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object"); 201 const UniValue& request = valRequest.get_obj(); 202 203 // Parse id now so errors from here on will have the id 204 if (request.exists("id")) { 205 id = request.find_value("id"); 206 } else { 207 id = std::nullopt; 208 } 209 210 // Check for JSON-RPC 2.0 (default 1.1) 211 m_json_version = JSONRPCVersion::V1_LEGACY; 212 const UniValue& jsonrpc_version = request.find_value("jsonrpc"); 213 if (!jsonrpc_version.isNull()) { 214 if (!jsonrpc_version.isStr()) { 215 throw JSONRPCError(RPC_INVALID_REQUEST, "jsonrpc field must be a string"); 216 } 217 // The "jsonrpc" key was added in the 2.0 spec, but some older documentation 218 // incorrectly included {"jsonrpc":"1.0"} in a request object, so we 219 // maintain that for backwards compatibility. 220 if (jsonrpc_version.get_str() == "1.0") { 221 m_json_version = JSONRPCVersion::V1_LEGACY; 222 } else if (jsonrpc_version.get_str() == "2.0") { 223 m_json_version = JSONRPCVersion::V2; 224 } else { 225 throw JSONRPCError(RPC_INVALID_REQUEST, "JSON-RPC version not supported"); 226 } 227 } 228 229 // Parse method 230 const UniValue& valMethod{request.find_value("method")}; 231 if (valMethod.isNull()) 232 throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method"); 233 if (!valMethod.isStr()) 234 throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); 235 strMethod = valMethod.get_str(); 236 const std::string log_id{id && !id->isNull() ? SanitizeString(id->getValStr()) : ""}; 237 if (fLogIPs) 238 LogDebug(BCLog::RPC, "ThreadRPCServer method=%s user=%s peeraddr=%s id=%s", SanitizeString(strMethod), 239 this->authUser, this->peerAddr, log_id); 240 else 241 LogDebug(BCLog::RPC, "ThreadRPCServer method=%s user=%s id=%s", SanitizeString(strMethod), this->authUser, 242 log_id); 243 244 // Parse params 245 const UniValue& valParams{request.find_value("params")}; 246 if (valParams.isArray() || valParams.isObject()) 247 params = valParams; 248 else if (valParams.isNull()) 249 params = UniValue(UniValue::VARR); 250 else 251 throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object"); 252 }