1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
|
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2022 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 <rpc/request.h>
#include <common/args.h>
#include <logging.h>
#include <random.h>
#include <rpc/protocol.h>
#include <util/fs.h>
#include <util/fs_helpers.h>
#include <util/strencodings.h>
#include <fstream>
#include <stdexcept>
#include <string>
#include <vector>
/**
* JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility,
* but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
* unspecified (HTTP errors and contents of 'error').
*
* 1.0 spec: http://json-rpc.org/wiki/specification
* 1.2 spec: http://jsonrpc.org/historical/json-rpc-over-http.html
*/
UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id)
{
UniValue request(UniValue::VOBJ);
request.pushKV("method", strMethod);
request.pushKV("params", params);
request.pushKV("id", id);
return request;
}
UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id)
{
UniValue reply(UniValue::VOBJ);
if (!error.isNull())
reply.pushKV("result", NullUniValue);
else
reply.pushKV("result", result);
reply.pushKV("error", error);
reply.pushKV("id", id);
return reply;
}
std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id)
{
UniValue reply = JSONRPCReplyObj(result, error, id);
return reply.write() + "\n";
}
UniValue JSONRPCError(int code, const std::string& message)
{
UniValue error(UniValue::VOBJ);
error.pushKV("code", code);
error.pushKV("message", message);
return error;
}
/** Username used when cookie authentication is in use (arbitrary, only for
* recognizability in debugging/logging purposes)
*/
static const std::string COOKIEAUTH_USER = "__cookie__";
/** Default name for auth cookie file */
static const char* const COOKIEAUTH_FILE = ".cookie";
/** Get name of RPC authentication cookie file */
static fs::path GetAuthCookieFile(bool temp=false)
{
fs::path arg = gArgs.GetPathArg("-rpccookiefile", COOKIEAUTH_FILE);
if (temp) {
arg += ".tmp";
}
return AbsPathForConfigVal(gArgs, arg);
}
static bool g_generated_cookie = false;
bool GenerateAuthCookie(std::string* cookie_out, std::optional<fs::perms> cookie_perms)
{
const size_t COOKIE_SIZE = 32;
unsigned char rand_pwd[COOKIE_SIZE];
GetRandBytes(rand_pwd);
std::string cookie = COOKIEAUTH_USER + ":" + HexStr(rand_pwd);
/** the umask determines what permissions are used to create this file -
* these are set to 0077 in common/system.cpp.
*/
std::ofstream file;
fs::path filepath_tmp = GetAuthCookieFile(true);
file.open(filepath_tmp);
if (!file.is_open()) {
LogInfo("Unable to open cookie authentication file %s for writing\n", fs::PathToString(filepath_tmp));
return false;
}
file << cookie;
file.close();
fs::path filepath = GetAuthCookieFile(false);
if (!RenameOver(filepath_tmp, filepath)) {
LogInfo("Unable to rename cookie authentication file %s to %s\n", fs::PathToString(filepath_tmp), fs::PathToString(filepath));
return false;
}
if (cookie_perms) {
std::error_code code;
fs::permissions(filepath, cookie_perms.value(), fs::perm_options::replace, code);
if (code) {
LogInfo("Unable to set permissions on cookie authentication file %s\n", fs::PathToString(filepath_tmp));
return false;
}
}
g_generated_cookie = true;
LogInfo("Generated RPC authentication cookie %s\n", fs::PathToString(filepath));
LogInfo("Permissions used for cookie: %s\n", PermsToSymbolicString(fs::status(filepath).permissions()));
if (cookie_out)
*cookie_out = cookie;
return true;
}
bool GetAuthCookie(std::string *cookie_out)
{
std::ifstream file;
std::string cookie;
fs::path filepath = GetAuthCookieFile();
file.open(filepath);
if (!file.is_open())
return false;
std::getline(file, cookie);
file.close();
if (cookie_out)
*cookie_out = cookie;
return true;
}
void DeleteAuthCookie()
{
try {
if (g_generated_cookie) {
// Delete the cookie file if it was generated by this process
fs::remove(GetAuthCookieFile());
}
} catch (const fs::filesystem_error& e) {
LogPrintf("%s: Unable to remove random auth cookie file: %s\n", __func__, fsbridge::get_filesystem_error_message(e));
}
}
std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in)
{
if (!in.isArray()) {
throw std::runtime_error("Batch must be an array");
}
const size_t num {in.size()};
std::vector<UniValue> batch(num);
for (const UniValue& rec : in.getValues()) {
if (!rec.isObject()) {
throw std::runtime_error("Batch member must be an object");
}
size_t id = rec["id"].getInt<int>();
if (id >= num) {
throw std::runtime_error("Batch member id is larger than batch size");
}
batch[id] = rec;
}
return batch;
}
void JSONRPCRequest::parse(const UniValue& valRequest)
{
// Parse request
if (!valRequest.isObject())
throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object");
const UniValue& request = valRequest.get_obj();
// Parse id now so errors from here on will have the id
id = request.find_value("id");
// Parse method
const UniValue& valMethod{request.find_value("method")};
if (valMethod.isNull())
throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method");
if (!valMethod.isStr())
throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string");
strMethod = valMethod.get_str();
if (fLogIPs)
LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s peeraddr=%s\n", SanitizeString(strMethod),
this->authUser, this->peerAddr);
else
LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s\n", SanitizeString(strMethod), this->authUser);
// Parse params
const UniValue& valParams{request.find_value("params")};
if (valParams.isArray() || valParams.isObject())
params = valParams;
else if (valParams.isNull())
params = UniValue(UniValue::VARR);
else
throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object");
}
|