Line data Source code
1 : // Copyright (c) 2009-2010 Satoshi Nakamoto
2 : // Copyright (c) 2009-2021 The Bitcoin developers
3 : // Copyright (c) 2009-2015 The Dash developers
4 : // Copyright (c) 2015-2022 The PIVX Core developers
5 : // Distributed under the MIT/X11 software license, see the accompanying
6 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
7 :
8 : #if defined(HAVE_CONFIG_H)
9 : #include "config/pivx-config.h"
10 : #endif
11 :
12 : #include "chainparamsbase.h"
13 : #include "clientversion.h"
14 : #include "fs.h"
15 : #include "rpc/client.h"
16 : #include "rpc/protocol.h"
17 : #include "util/system.h"
18 : #include "utilstrencodings.h"
19 :
20 : #include <stdio.h>
21 : #include <tuple>
22 :
23 : #include <event2/buffer.h>
24 : #include <event2/keyvalq_struct.h>
25 : #include "support/events.h"
26 :
27 : #include <univalue.h>
28 :
29 : static const char DEFAULT_RPCCONNECT[] = "127.0.0.1";
30 : static const bool DEFAULT_NAMED=false;
31 : static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
32 : static const int CONTINUE_EXECUTION=-1;
33 :
34 0 : std::string HelpMessageCli()
35 : {
36 0 : const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN);
37 0 : const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET);
38 0 : std::string strUsage;
39 0 : strUsage += HelpMessageGroup("Options:");
40 0 : strUsage += HelpMessageOpt("-?", "This help message");
41 0 : strUsage += HelpMessageOpt("-conf=<file>", strprintf("Specify configuration file (default: %s)", PIVX_CONF_FILENAME));
42 0 : strUsage += HelpMessageOpt("-datadir=<dir>", "Specify data directory");
43 0 : AppendParamsHelpMessages(strUsage);
44 0 : strUsage += HelpMessageOpt("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED));
45 0 : strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT));
46 0 : strUsage += HelpMessageOpt("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u or testnet: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort()));
47 0 : strUsage += HelpMessageOpt("-rpcwait", "Wait for RPC server to start");
48 0 : strUsage += HelpMessageOpt("-rpcuser=<user>", "Username for JSON-RPC connections");
49 0 : strUsage += HelpMessageOpt("-rpcpassword=<pw>", "Password for JSON-RPC connections");
50 0 : strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT));
51 0 : strUsage += HelpMessageOpt("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to pivxd)");
52 :
53 0 : return strUsage;
54 : }
55 :
56 : /** libevent event log callback */
57 0 : static void libevent_log_cb(int severity, const char *msg)
58 : {
59 : #ifndef EVENT_LOG_ERR // EVENT_LOG_ERR was added in 2.0.19; but before then _EVENT_LOG_ERR existed.
60 : # define EVENT_LOG_ERR _EVENT_LOG_ERR
61 : #endif
62 : // Ignore everything other than errors
63 0 : if (severity >= EVENT_LOG_ERR) {
64 0 : throw std::runtime_error(strprintf("libevent error: %s", msg));
65 : }
66 0 : }
67 :
68 : //////////////////////////////////////////////////////////////////////////////
69 : //
70 : // Start
71 : //
72 :
73 : //
74 : // Exception thrown on connection error. This error is used to determine
75 : // when to wait if -rpcwait is given.
76 : //
77 : class CConnectionFailed : public std::runtime_error
78 : {
79 : public:
80 0 : explicit inline CConnectionFailed(const std::string& msg) : std::runtime_error(msg)
81 : {
82 : }
83 : };
84 :
85 : //
86 : // This function returns either one of EXIT_ codes when it's expected to stop the process or
87 : // CONTINUE_EXECUTION when it's expected to continue further.
88 : //
89 3 : static int AppInitRPC(int argc, char* argv[])
90 : {
91 : //
92 : // Parameters
93 : //
94 3 : gArgs.ParseParameters(argc, argv);
95 15 : if (argc < 2 || gArgs.IsArgSet("-?") || gArgs.IsArgSet("-h") || gArgs.IsArgSet("-help") || gArgs.IsArgSet("-version")) {
96 0 : std::string strUsage = PACKAGE_NAME " RPC client version " + FormatFullVersion() + "\n";
97 0 : if (!gArgs.IsArgSet("-version")) {
98 0 : strUsage += "\n"
99 : "Usage: pivx-cli [options] <command> [params] Send command to " PACKAGE_NAME "\n"
100 : "or: pivx-cli [options] -named <command> [name=value]... Send command to " PACKAGE_NAME " (with named arguments)\n"
101 : "or: pivx-cli [options] help List commands\n"
102 0 : "or: pivx-cli [options] help <command> Get help for a command\n";
103 0 : strUsage += "\n" + HelpMessageCli();
104 : }
105 :
106 0 : fprintf(stdout, "%s", strUsage.c_str());
107 0 : if (argc < 2) {
108 0 : fprintf(stderr, "Error: too few parameters\n");
109 : return EXIT_FAILURE;
110 : }
111 : return EXIT_SUCCESS;
112 : }
113 3 : if (!CheckDataDirOption()) {
114 0 : fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str());
115 0 : return EXIT_FAILURE;
116 : }
117 3 : try {
118 6 : gArgs.ReadConfigFile(gArgs.GetArg("-conf", PIVX_CONF_FILENAME));
119 0 : } catch (const std::exception& e) {
120 0 : fprintf(stderr, "Error reading configuration file: %s\n", e.what());
121 0 : return EXIT_FAILURE;
122 : }
123 : // Check for -testnet or -regtest parameter (BaseParams() calls are only valid after this clause)
124 3 : try {
125 3 : SelectBaseParams(gArgs.GetChainName());
126 0 : } catch(const std::exception& e) {
127 0 : fprintf(stderr, "Error: %s\n", e.what());
128 0 : return EXIT_FAILURE;
129 : }
130 3 : if (gArgs.GetBoolArg("-rpcssl", false))
131 : {
132 0 : fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n");
133 0 : return EXIT_FAILURE;
134 : }
135 : return CONTINUE_EXECUTION;
136 : }
137 :
138 :
139 : /** Reply structure for request_done to fill in */
140 0 : struct HTTPReply
141 : {
142 3 : HTTPReply(): status(0), error(-1) {}
143 :
144 : int status;
145 : int error;
146 : std::string body;
147 : };
148 :
149 0 : const char *http_errorstring(int code)
150 : {
151 0 : switch(code) {
152 : #if LIBEVENT_VERSION_NUMBER >= 0x02010300
153 : case EVREQ_HTTP_TIMEOUT:
154 : return "timeout reached";
155 0 : case EVREQ_HTTP_EOF:
156 0 : return "EOF reached";
157 0 : case EVREQ_HTTP_INVALID_HEADER:
158 0 : return "error while reading header, or invalid header";
159 0 : case EVREQ_HTTP_BUFFER_ERROR:
160 0 : return "error encountered while reading or writing";
161 0 : case EVREQ_HTTP_REQUEST_CANCEL:
162 0 : return "request was canceled";
163 0 : case EVREQ_HTTP_DATA_TOO_LONG:
164 0 : return "response body is larger than allowed";
165 : #endif
166 0 : default:
167 0 : return "unknown";
168 : }
169 : }
170 :
171 3 : static void http_request_done(struct evhttp_request *req, void *ctx)
172 : {
173 3 : HTTPReply *reply = static_cast<HTTPReply*>(ctx);
174 :
175 3 : if (req == nullptr) {
176 : /* If req is nullptr, it means an error occurred while connecting: the
177 : * error code will have been passed to http_error_cb.
178 : */
179 0 : reply->status = 0;
180 0 : return;
181 : }
182 :
183 3 : reply->status = evhttp_request_get_response_code(req);
184 :
185 3 : struct evbuffer *buf = evhttp_request_get_input_buffer(req);
186 3 : if (buf)
187 : {
188 3 : size_t size = evbuffer_get_length(buf);
189 3 : const char *data = (const char*)evbuffer_pullup(buf, size);
190 3 : if (data)
191 3 : reply->body = std::string(data, size);
192 3 : evbuffer_drain(buf, size);
193 : }
194 : }
195 :
196 : #if LIBEVENT_VERSION_NUMBER >= 0x02010300
197 0 : static void http_error_cb(enum evhttp_request_error err, void *ctx)
198 : {
199 0 : HTTPReply *reply = static_cast<HTTPReply*>(ctx);
200 0 : reply->error = err;
201 0 : }
202 : #endif
203 :
204 3 : UniValue CallRPC(const std::string& strMethod, const UniValue& params)
205 : {
206 6 : std::string host = gArgs.GetArg("-rpcconnect", DEFAULT_RPCCONNECT);
207 3 : int port = gArgs.GetArg("-rpcport", BaseParams().RPCPort());
208 :
209 : // Obtain event base
210 6 : raii_event_base base = obtain_event_base();
211 :
212 : // Synchronously look up hostname
213 9 : raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port);
214 3 : evhttp_connection_set_timeout(evcon.get(), gArgs.GetArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT));
215 :
216 6 : HTTPReply response;
217 6 : raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response);
218 3 : if (req == nullptr)
219 0 : throw std::runtime_error("create http request failed");
220 : #if LIBEVENT_VERSION_NUMBER >= 0x02010300
221 3 : evhttp_request_set_error_cb(req.get(), http_error_cb);
222 : #endif
223 :
224 : // Get credentials
225 6 : std::string strRPCUserColonPass;
226 6 : if (gArgs.GetArg("-rpcpassword", "") == "") {
227 : // Try fall back to cookie-based authentication if no password is provided
228 3 : if (!GetAuthCookie(&strRPCUserColonPass)) {
229 0 : throw std::runtime_error(strprintf(
230 0 : _("Could not locate RPC credentials. No authentication cookie could be found, and no rpcpassword is set in the configuration file (%s)"),
231 0 : GetConfigFile(gArgs.GetArg("-conf", PIVX_CONF_FILENAME)).string().c_str()));
232 :
233 : }
234 : } else {
235 0 : strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
236 : }
237 :
238 3 : struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get());
239 3 : assert(output_headers);
240 3 : evhttp_add_header(output_headers, "Host", host.c_str());
241 3 : evhttp_add_header(output_headers, "Connection", "close");
242 6 : evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
243 :
244 : // Attach request data
245 9 : std::string strRequest = JSONRPCRequestObj(strMethod, params, 1).write() + "\n";
246 3 : struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get());
247 3 : assert(output_buffer);
248 3 : evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
249 :
250 : // check if we should use a special wallet endpoint
251 6 : std::string endpoint = "/";
252 3 : if (!gArgs.GetArgs("-rpcwallet").empty()) {
253 0 : std::string walletName = gArgs.GetArg("-rpcwallet", "");
254 0 : char* encodedURI = evhttp_uriencode(walletName.c_str(), walletName.size(), false);
255 0 : if (encodedURI) {
256 0 : endpoint = "/wallet/"+ std::string(encodedURI);
257 0 : free(encodedURI);
258 : } else {
259 0 : throw CConnectionFailed("uri-encode failed");
260 : }
261 : }
262 3 : int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/");
263 3 : req.release(); // ownership moved to evcon in above call
264 3 : if (r != 0) {
265 0 : throw CConnectionFailed("send http request failed");
266 : }
267 :
268 3 : event_base_dispatch(base.get());
269 :
270 3 : if (response.status == 0)
271 0 : throw CConnectionFailed(strprintf("couldn't connect to server: %s (code %d)\n(make sure server is running and you are connecting to the correct RPC port)", http_errorstring(response.error), response.error));
272 3 : else if (response.status == HTTP_UNAUTHORIZED)
273 0 : throw std::runtime_error("incorrect rpcuser or rpcpassword (authorization failed)");
274 3 : else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR)
275 0 : throw std::runtime_error(strprintf("server returned HTTP error %d", response.status));
276 3 : else if (response.body.empty())
277 0 : throw std::runtime_error("no response from server");
278 :
279 : // Parse reply
280 6 : UniValue valReply(UniValue::VSTR);
281 3 : if (!valReply.read(response.body))
282 0 : throw std::runtime_error("couldn't parse reply from server");
283 3 : const UniValue& reply = valReply.get_obj();
284 3 : if (reply.empty())
285 0 : throw std::runtime_error("expected reply to have result, error and id properties");
286 :
287 6 : return reply;
288 : }
289 :
290 3 : int CommandLineRPC(int argc, char* argv[])
291 : {
292 3 : std::string strPrint;
293 3 : int nRet = 0;
294 6 : try {
295 : // Skip switches
296 6 : while (argc > 1 && IsSwitchChar(argv[1][0])) {
297 3 : argc--;
298 3 : argv++;
299 : }
300 :
301 : // Method
302 3 : if (argc < 2)
303 0 : throw std::runtime_error("too few parameters");
304 6 : std::string strMethod = argv[1];
305 :
306 : // Parameters default to strings
307 6 : std::vector<std::string> strParams(&argv[2], &argv[argc]);
308 6 : UniValue params;
309 3 : if(gArgs.GetBoolArg("-named", DEFAULT_NAMED)) {
310 0 : params = RPCConvertNamedValues(strMethod, strParams);
311 : } else {
312 3 : params = RPCConvertValues(strMethod, strParams);
313 : }
314 :
315 : // Execute and handle connection failures with -rpcwait
316 3 : const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
317 3 : do {
318 3 : try {
319 3 : const UniValue reply = CallRPC(strMethod, params);
320 :
321 : // Parse reply
322 3 : const UniValue& result = find_value(reply, "result");
323 3 : const UniValue& error = find_value(reply, "error");
324 :
325 3 : if (!error.isNull()) {
326 : // Error
327 0 : int code = error["code"].get_int();
328 0 : if (fWait && code == RPC_IN_WARMUP)
329 0 : throw CConnectionFailed("server in warmup");
330 0 : strPrint = "error: " + error.write();
331 0 : nRet = abs(code);
332 0 : if (error.isObject()) {
333 0 : UniValue errCode = find_value(error, "code");
334 0 : UniValue errMsg = find_value(error, "message");
335 0 : strPrint = errCode.isNull() ? "" : "error code: "+errCode.getValStr()+"\n";
336 :
337 0 : if (errMsg.isStr())
338 0 : strPrint += "error message:\n"+errMsg.get_str();
339 :
340 0 : if (errCode.isNum() && errCode.get_int() == RPC_WALLET_NOT_SPECIFIED) {
341 0 : strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to pivx-cli command line.";
342 : }
343 : }
344 : } else {
345 : // Result
346 3 : if (result.isNull())
347 0 : strPrint = "";
348 3 : else if (result.isStr())
349 0 : strPrint = result.get_str();
350 : else
351 3 : strPrint = result.write(2);
352 : }
353 : // Connection succeeded, no need to retry.
354 3 : break;
355 0 : } catch (const CConnectionFailed& e) {
356 0 : if (fWait)
357 0 : MilliSleep(1000);
358 : else
359 0 : throw;
360 : }
361 : } while (fWait);
362 0 : } catch (const boost::thread_interrupted&) {
363 0 : throw;
364 0 : } catch (const std::exception& e) {
365 0 : strPrint = std::string("error: ") + e.what();
366 0 : nRet = EXIT_FAILURE;
367 0 : } catch (...) {
368 0 : PrintExceptionContinue(nullptr, "CommandLineRPC()");
369 0 : throw;
370 : }
371 :
372 3 : if (strPrint != "") {
373 3 : fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str());
374 : }
375 6 : return nRet;
376 : }
377 :
378 : #ifdef WIN32
379 : // Export main() and ensure working ASLR on Windows.
380 : // Exporting a symbol will prevent the linker from stripping
381 : // the .reloc section from the binary, which is a requirement
382 : // for ASLR. This is a temporary workaround until a fixed
383 : // version of binutils is used for releases.
384 : __declspec(dllexport) int main(int argc, char* argv[])
385 : {
386 : util::WinCmdLineArgs winArgs;
387 : std::tie(argc, argv) = winArgs.get();
388 : #else
389 3 : int main(int argc, char* argv[])
390 : {
391 : #endif
392 3 : SetupEnvironment();
393 3 : if (!SetupNetworking()) {
394 0 : fprintf(stderr, "Error: Initializing networking failed\n");
395 0 : return EXIT_FAILURE;
396 : }
397 3 : event_set_log_callback(&libevent_log_cb);
398 :
399 3 : try {
400 3 : int ret = AppInitRPC(argc, argv);
401 3 : if (ret != CONTINUE_EXECUTION)
402 : return ret;
403 0 : } catch (const std::exception& e) {
404 0 : PrintExceptionContinue(&e, "AppInitRPC()");
405 0 : return EXIT_FAILURE;
406 0 : } catch (...) {
407 0 : PrintExceptionContinue(nullptr, "AppInitRPC()");
408 0 : return EXIT_FAILURE;
409 : }
410 :
411 3 : int ret = EXIT_FAILURE;
412 3 : try {
413 3 : ret = CommandLineRPC(argc, argv);
414 0 : } catch (const std::exception& e) {
415 0 : PrintExceptionContinue(&e, "CommandLineRPC()");
416 0 : } catch (...) {
417 0 : PrintExceptionContinue(nullptr, "CommandLineRPC()");
418 : }
419 : return ret;
420 : }
|