mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 19:45:53 +00:00
This makes it easy to add self-contained modules that can be controlled and is also useful for one-off and troubleshooting commands.
696 lines
18 KiB
C++
696 lines
18 KiB
C++
|
|
#include <iostream>
|
|
#include <cstdlib>
|
|
|
|
#include <boost/asio.hpp>
|
|
#include <boost/iostreams/concepts.hpp>
|
|
#include <boost/iostreams/stream.hpp>
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
#include <openssl/buffer.h>
|
|
#include <openssl/evp.h>
|
|
|
|
#include "../json/value.h"
|
|
#include "../json/reader.h"
|
|
|
|
#include "RPC.h"
|
|
#include "Log.h"
|
|
#include "RPCErr.h"
|
|
#include "Config.h"
|
|
#include "BitcoinUtil.h"
|
|
|
|
#include "CallRPC.h"
|
|
|
|
SETUP_LOG();
|
|
|
|
static inline bool isSwitchChar(char c)
|
|
{
|
|
#ifdef __WXMSW__
|
|
return c == '-' || c == '/';
|
|
#else
|
|
return c == '-';
|
|
#endif
|
|
}
|
|
|
|
std::string EncodeBase64(const std::string& s)
|
|
{ // FIXME: This performs terribly
|
|
BIO *b64, *bmem;
|
|
BUF_MEM *bptr;
|
|
|
|
b64 = BIO_new(BIO_f_base64());
|
|
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
|
bmem = BIO_new(BIO_s_mem());
|
|
b64 = BIO_push(b64, bmem);
|
|
BIO_write(b64, s.data(), s.size());
|
|
(void) BIO_flush(b64);
|
|
BIO_get_mem_ptr(b64, &bptr);
|
|
|
|
std::string result(bptr->data, bptr->length);
|
|
BIO_free_all(b64);
|
|
|
|
return result;
|
|
}
|
|
|
|
Json::Value RPCParser::parseAsIs(const Json::Value& jvParams)
|
|
{
|
|
Json::Value v(Json::objectValue);
|
|
if (jvParams.isArray() && (jvParams.size() > 0))
|
|
v["params"] = jvParams;
|
|
return v;
|
|
}
|
|
|
|
Json::Value RPCParser::parseInternal(const Json::Value& jvParams)
|
|
{
|
|
Json::Value v(Json::objectValue);
|
|
v["internal_command"] = jvParams[0u];
|
|
|
|
Json::Value params(Json::arrayValue);
|
|
for (unsigned i = 1; i < jvParams.size(); ++i)
|
|
params.append(jvParams[i]);
|
|
v["params"] = params;
|
|
|
|
return v;
|
|
}
|
|
|
|
// account_info <account>|<nickname>|<account_public_key>
|
|
// account_info <seed>|<pass_phrase>|<key> [<index>]
|
|
Json::Value RPCParser::parseAccountInfo(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest(Json::objectValue);
|
|
std::string strIdent = jvParams[0u].asString();
|
|
// YYY This could be more strict and report casting errors.
|
|
int iIndex = 2 == jvParams.size() ? lexical_cast_s<int>(jvParams[1u].asString()) : 0;
|
|
|
|
RippleAddress raAddress;
|
|
|
|
if (!raAddress.setAccountPublic(strIdent) && !raAddress.setAccountID(strIdent) && !raAddress.setSeedGeneric(strIdent))
|
|
return rpcError(rpcACT_MALFORMED);
|
|
|
|
jvRequest["ident"] = strIdent;
|
|
jvRequest["account_index"] = iIndex;
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
// account_tx <account> <minledger> <maxledger>
|
|
// account_tx <account> <ledger>
|
|
Json::Value RPCParser::parseAccountTransactions(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest(Json::objectValue);
|
|
RippleAddress raAccount;
|
|
|
|
if (jvParams.size() < 2 || jvParams.size() > 3)
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
|
|
if (!raAccount.setAccountID(jvParams[0u].asString()))
|
|
return rpcError(rpcACT_MALFORMED);
|
|
|
|
// YYY This could be more strict and report casting errors.
|
|
if (jvParams.size() == 2)
|
|
{
|
|
jvRequest["ledger"] = jvParams[1u].asUInt();
|
|
}
|
|
else
|
|
{
|
|
uint32 uLedgerMin = jvParams[1u].asUInt();
|
|
uint32 uLedgerMax = jvParams[2u].asUInt();
|
|
|
|
if ((uLedgerMax < uLedgerMin) || (uLedgerMax == 0))
|
|
{
|
|
return rpcError(rpcLGR_IDXS_INVALID);
|
|
}
|
|
|
|
jvRequest["ledger_min"] = uLedgerMin;
|
|
jvRequest["ledger_max"] = uLedgerMax;
|
|
}
|
|
|
|
jvRequest["account"] = raAccount.humanAccountID();
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
// connect <ip> [port]
|
|
Json::Value RPCParser::parseConnect(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest(Json::objectValue);
|
|
|
|
jvRequest["ip"] = jvParams[0u].asString();
|
|
|
|
if (jvParams.size() == 2)
|
|
jvRequest["port"] = jvParams[1u].asUInt();
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
#if ENABLE_INSECURE
|
|
// data_delete <key>
|
|
Json::Value RPCParser::parseDataDelete(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest(Json::objectValue);
|
|
|
|
jvRequest["key"] = jvParams[0u].asString();
|
|
|
|
return jvRequest;
|
|
}
|
|
#endif
|
|
|
|
#if ENABLE_INSECURE
|
|
// data_fetch <key>
|
|
Json::Value RPCParser::parseDataFetch(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest(Json::objectValue);
|
|
|
|
jvRequest["key"] = jvParams[0u].asString();
|
|
|
|
return jvRequest;
|
|
}
|
|
#endif
|
|
|
|
#if ENABLE_INSECURE
|
|
// data_store <key> <value>
|
|
Json::Value RPCParser::parseDataStore(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest(Json::objectValue);
|
|
|
|
jvRequest["key"] = jvParams[0u].asString();
|
|
jvRequest["value"] = jvParams[1u].asString();
|
|
|
|
return jvRequest;
|
|
}
|
|
#endif
|
|
|
|
// Return an error for attemping to subscribe/unsubscribe via RPC.
|
|
Json::Value RPCParser::parseEvented(const Json::Value& jvParams)
|
|
{
|
|
return rpcError(rpcNO_EVENTS);
|
|
}
|
|
|
|
// get_counts [<min_count>]
|
|
Json::Value RPCParser::parseGetCounts(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest(Json::objectValue);
|
|
|
|
if (jvParams.size())
|
|
jvRequest["min_count"] = jvParams[0u].asUInt();
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
// ledger [id|index|current|closed] [full]
|
|
Json::Value RPCParser::parseLedger(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest(Json::objectValue);
|
|
|
|
if (!jvParams.size())
|
|
{
|
|
return jvRequest;
|
|
}
|
|
|
|
std::string strLedger = jvParams[0u].asString();
|
|
|
|
if (strLedger == "current" || strLedger == "closed" || strLedger.length() > 12)
|
|
{
|
|
jvRequest["ledger"] = strLedger;
|
|
}
|
|
else
|
|
{
|
|
jvRequest["ledger"] = lexical_cast_s<uint32>(strLedger);
|
|
}
|
|
|
|
if (2 == jvParams.size() && jvParams[1u].asString() == "full")
|
|
{
|
|
jvRequest["full"] = bool(1);
|
|
}
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
#if ENABLE_INSECURE
|
|
// login <username> <password>
|
|
Json::Value RPCParser::parseLogin(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest(Json::objectValue);
|
|
|
|
jvRequest["username"] = jvParams[0u].asString();
|
|
jvRequest["password"] = jvParams[1u].asString();
|
|
|
|
return jvRequest;
|
|
}
|
|
#endif
|
|
|
|
// log_level: Get log levels
|
|
// log_level <severity>: Set master log level to the specified severity
|
|
// log_level <partition> <severity>: Set specified partition to specified severity
|
|
Json::Value RPCParser::parseLogLevel(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest(Json::objectValue);
|
|
|
|
if (jvParams.size() == 1)
|
|
{
|
|
jvRequest["severity"] = jvParams[0u].asString();
|
|
}
|
|
else if (jvParams.size() == 2)
|
|
{
|
|
jvRequest["partition"] = jvParams[0u].asString();
|
|
jvRequest["severity"] = jvParams[1u].asString();
|
|
}
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
// owner_info <account>|<nickname>|<account_public_key>
|
|
// owner_info <seed>|<pass_phrase>|<key> [<index>]
|
|
Json::Value RPCParser::parseOwnerInfo(const Json::Value& jvParams)
|
|
{
|
|
return parseAccountInfo(jvParams);
|
|
}
|
|
|
|
// account_lines <account>|<nickname>|<account_public_key> [<index>]
|
|
// account_offers <account>|<nickname>|<account_public_key> [<index>]
|
|
Json::Value RPCParser::parseAccountItems(const Json::Value& jvParams)
|
|
{
|
|
std::string strIdent = jvParams[0u].asString();
|
|
bool bIndex = 2 == jvParams.size();
|
|
int iIndex = bIndex ? lexical_cast_s<int>(jvParams[1u].asString()) : 0;
|
|
|
|
if (bIndex && !iIndex) // Don't send default.
|
|
bIndex = false;
|
|
|
|
// Get info on account.
|
|
Json::Value jvRequest(Json::objectValue);
|
|
|
|
jvRequest["account"] = strIdent;
|
|
if (bIndex)
|
|
jvRequest["account_index"] = iIndex;
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
// ripple_path_find json
|
|
Json::Value RPCParser::parseRipplePathFind(const Json::Value& jvParams)
|
|
{
|
|
Json::Value txJSON;
|
|
Json::Reader reader;
|
|
|
|
cLog(lsTRACE) << "RPC json:" << jvParams[0u];
|
|
if (reader.parse(jvParams[0u].asString(), txJSON))
|
|
{
|
|
return txJSON;
|
|
}
|
|
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
}
|
|
|
|
// submit any transaction to the network
|
|
// submit private_key json
|
|
Json::Value RPCParser::parseSubmit(const Json::Value& jvParams)
|
|
{
|
|
Json::Value txJSON;
|
|
Json::Reader reader;
|
|
|
|
if (reader.parse(jvParams[1u].asString(), txJSON))
|
|
{
|
|
Json::Value jvRequest;
|
|
|
|
jvRequest["secret"] = jvParams[0u].asString();
|
|
jvRequest["tx_json"] = txJSON;
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
}
|
|
|
|
// tx <transaction_id>
|
|
Json::Value RPCParser::parseTx(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest;
|
|
|
|
jvRequest["transaction"] = jvParams[0u].asString();
|
|
return jvRequest;
|
|
}
|
|
|
|
// tx_history <index>
|
|
Json::Value RPCParser::parseTxHistory(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest;
|
|
|
|
jvRequest["start"] = jvParams[0u].asUInt();
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
// unl_add <domain>|<node_public> [<comment>]
|
|
Json::Value RPCParser::parseUnlAdd(const Json::Value& jvParams)
|
|
{
|
|
std::string strNode = jvParams[0u].asString();
|
|
std::string strComment = (jvParams.size() == 2) ? jvParams[1u].asString() : "";
|
|
|
|
RippleAddress naNodePublic;
|
|
|
|
if (strNode.length())
|
|
{
|
|
Json::Value jvRequest;
|
|
|
|
jvRequest["node"] = strNode;
|
|
|
|
if (strComment.length())
|
|
jvRequest["comment"] = strComment;
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
}
|
|
|
|
// unl_delete <domain>|<public_key>
|
|
Json::Value RPCParser::parseUnlDelete(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest;
|
|
|
|
jvRequest["node"] = jvParams[0u].asString();
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
// validation_create [<pass_phrase>|<seed>|<seed_key>]
|
|
//
|
|
// NOTE: It is poor security to specify secret information on the command line. This information might be saved in the command
|
|
// shell history file (e.g. .bash_history) and it may be leaked via the process status command (i.e. ps).
|
|
Json::Value RPCParser::parseValidationCreate(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest;
|
|
|
|
if (jvParams.size())
|
|
jvRequest["secret"] = jvParams[0u].asString();
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
// validation_seed [<pass_phrase>|<seed>|<seed_key>]
|
|
//
|
|
// NOTE: It is poor security to specify secret information on the command line. This information might be saved in the command
|
|
// shell history file (e.g. .bash_history) and it may be leaked via the process status command (i.e. ps).
|
|
Json::Value RPCParser::parseValidationSeed(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest;
|
|
|
|
if (jvParams.size())
|
|
jvRequest["secret"] = jvParams[0u].asString();
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
// wallet_accounts <seed>
|
|
Json::Value RPCParser::parseWalletAccounts(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest;
|
|
|
|
jvRequest["seed"] = jvParams[0u].asString();
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
// wallet_propose [<passphrase>]
|
|
// <passphrase> is only for testing. Master seeds should only be generated randomly.
|
|
Json::Value RPCParser::parseWalletPropose(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest;
|
|
|
|
if (jvParams.size())
|
|
jvRequest["passphrase"] = jvParams[0u].asString();
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
// wallet_seed [<seed>|<passphrase>|<passkey>]
|
|
Json::Value RPCParser::parseWalletSeed(const Json::Value& jvParams)
|
|
{
|
|
Json::Value jvRequest;
|
|
|
|
if (jvParams.size())
|
|
jvRequest["secret"] = jvParams[0u].asString();
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
//
|
|
// parseCommand
|
|
//
|
|
|
|
// Convert a rpc method and params to a request.
|
|
// <-- { method: xyz, params: [... ] } or { error: ..., ... }
|
|
Json::Value RPCParser::parseCommand(std::string strMethod, Json::Value jvParams)
|
|
{
|
|
cLog(lsTRACE) << "RPC method:" << strMethod;
|
|
cLog(lsTRACE) << "RPC params:" << jvParams;
|
|
|
|
static struct {
|
|
const char* pCommand;
|
|
parseFuncPtr pfpFunc;
|
|
int iMinParams;
|
|
int iMaxParams;
|
|
} commandsA[] = {
|
|
// Request-response methods
|
|
// - Returns an error, or the request.
|
|
// - To modify the method, provide a new method in the request.
|
|
{ "accept_ledger", &RPCParser::parseAsIs, 0, 0 },
|
|
{ "account_info", &RPCParser::parseAccountInfo, 1, 2 },
|
|
{ "account_lines", &RPCParser::parseAccountItems, 1, 2 },
|
|
{ "account_offers", &RPCParser::parseAccountItems, 1, 2 },
|
|
{ "account_tx", &RPCParser::parseAccountTransactions, 2, 3 },
|
|
{ "connect", &RPCParser::parseConnect, 1, 2 },
|
|
{ "get_counts", &RPCParser::parseGetCounts, 0, 1 },
|
|
{ "ledger", &RPCParser::parseLedger, 0, 2 },
|
|
{ "ledger_accept", &RPCParser::parseAsIs, 0, 0 },
|
|
{ "ledger_closed", &RPCParser::parseAsIs, 0, 0 },
|
|
{ "ledger_current", &RPCParser::parseAsIs, 0, 0 },
|
|
// { "ledger_entry", &RPCParser::parseLedgerEntry, -1, -1 },
|
|
// { "ledger_header", &RPCParser::parseLedgerHeader, -1, -1 },
|
|
{ "log_level", &RPCParser::parseLogLevel, 0, 2 },
|
|
{ "logrotate", &RPCParser::parseAsIs, 0, 0 },
|
|
// { "nickname_info", &RPCParser::parseNicknameInfo, 1, 1 },
|
|
{ "owner_info", &RPCParser::parseOwnerInfo, 1, 2 },
|
|
{ "peers", &RPCParser::parseAsIs, 0, 0 },
|
|
// { "profile", &RPCParser::parseProfile, 1, 9 },
|
|
{ "random", &RPCParser::parseAsIs, 0, 0 },
|
|
{ "ripple_path_find", &RPCParser::parseRipplePathFind, 1, 1 },
|
|
{ "submit", &RPCParser::parseSubmit, 2, 2 },
|
|
{ "server_info", &RPCParser::parseAsIs, 0, 0 },
|
|
{ "stop", &RPCParser::parseAsIs, 0, 0 },
|
|
// { "transaction_entry", &RPCParser::parseTransactionEntry, -1, -1 },
|
|
{ "tx", &RPCParser::parseTx, 1, 1 },
|
|
{ "tx_history", &RPCParser::parseTxHistory, 1, 1 },
|
|
|
|
{ "unl_add", &RPCParser::parseUnlAdd, 1, 2 },
|
|
{ "unl_delete", &RPCParser::parseUnlDelete, 1, 1 },
|
|
{ "unl_list", &RPCParser::parseAsIs, 0, 0 },
|
|
{ "unl_load", &RPCParser::parseAsIs, 0, 0 },
|
|
{ "unl_network", &RPCParser::parseAsIs, 0, 0 },
|
|
{ "unl_reset", &RPCParser::parseAsIs, 0, 0 },
|
|
{ "unl_score", &RPCParser::parseAsIs, 0, 0 },
|
|
|
|
{ "validation_create", &RPCParser::parseValidationCreate, 0, 1 },
|
|
{ "validation_seed", &RPCParser::parseValidationSeed, 0, 1 },
|
|
|
|
{ "wallet_accounts", &RPCParser::parseWalletAccounts, 1, 1 },
|
|
{ "wallet_propose", &RPCParser::parseWalletPropose, 0, 1 },
|
|
{ "wallet_seed", &RPCParser::parseWalletSeed, 0, 1 },
|
|
|
|
{ "internal", &RPCParser::parseInternal, 1, -1 },
|
|
|
|
#if ENABLE_INSECURE
|
|
// XXX Unnecessary commands which should be removed.
|
|
{ "login", &RPCParser::parseLogin, 2, 2 },
|
|
{ "data_delete", &RPCParser::parseDataDelete, 1, 1 },
|
|
{ "data_fetch", &RPCParser::parseDataFetch, 1, 1 },
|
|
{ "data_store", &RPCParser::parseDataStore, 2, 2 },
|
|
#endif
|
|
|
|
// Evented methods
|
|
{ "subscribe", &RPCParser::parseEvented, -1, -1 },
|
|
{ "unsubscribe", &RPCParser::parseEvented, -1, -1 },
|
|
};
|
|
|
|
int i = NUMBER(commandsA);
|
|
|
|
while (i-- && strMethod != commandsA[i].pCommand)
|
|
;
|
|
|
|
if (i < 0)
|
|
{
|
|
return rpcError(rpcBAD_SYNTAX);
|
|
}
|
|
else if ((commandsA[i].iMinParams >= 0 && jvParams.size() < commandsA[i].iMinParams)
|
|
|| (commandsA[i].iMaxParams >= 0 && jvParams.size() > commandsA[i].iMaxParams))
|
|
{
|
|
cLog(lsWARNING) << "Wrong number of parameters: minimum=" << commandsA[i].iMinParams
|
|
<< " maximum=" << commandsA[i].iMaxParams
|
|
<< " actual=" << jvParams.size();
|
|
|
|
return rpcError(rpcBAD_SYNTAX);
|
|
}
|
|
|
|
return (this->*(commandsA[i].pfpFunc))(jvParams);
|
|
}
|
|
|
|
int commandLineRPC(const std::vector<std::string>& vCmd)
|
|
{
|
|
Json::Value jvOutput;
|
|
int nRet = 0;
|
|
Json::Value jvRequest(Json::objectValue);
|
|
|
|
try
|
|
{
|
|
RPCParser rpParser;
|
|
Json::Value jvRpcParams(Json::arrayValue);
|
|
|
|
if (theConfig.RPC_USER.empty() && theConfig.RPC_PASSWORD.empty())
|
|
throw std::runtime_error("You must set rpcpassword=<password> in the configuration file. "
|
|
"If the file does not exist, create it with owner-readable-only file permissions.");
|
|
|
|
if (vCmd.empty()) return 1; // 1 = print usage.
|
|
|
|
for (int i = 1; i != vCmd.size(); i++)
|
|
jvRpcParams.append(vCmd[i]);
|
|
|
|
Json::Value jvRpc = Json::Value(Json::objectValue);
|
|
|
|
jvRpc["method"] = vCmd[0];
|
|
jvRpc["params"] = jvRpcParams;
|
|
|
|
jvRequest = rpParser.parseCommand(vCmd[0], jvRpcParams);
|
|
|
|
// std::cerr << "Request: " << jvRequest << std::endl;
|
|
|
|
if (jvRequest.isMember("error"))
|
|
{
|
|
jvOutput = jvRequest;
|
|
jvOutput["rpc"] = jvRpc;
|
|
}
|
|
else
|
|
{
|
|
Json::Value jvParams(Json::arrayValue);
|
|
|
|
jvParams.append(jvRequest);
|
|
|
|
jvOutput = callRPC(
|
|
theConfig.RPC_IP,
|
|
theConfig.RPC_PORT,
|
|
theConfig.RPC_USER,
|
|
theConfig.RPC_PASSWORD,
|
|
"",
|
|
jvRequest.isMember("method") // Allow parser to rewrite method.
|
|
? jvRequest["method"].asString()
|
|
: vCmd[0],
|
|
jvParams); // Parsed, execute.
|
|
|
|
if (jvOutput.isMember("result"))
|
|
{
|
|
// Had a successful JSON-RPC 2.0 call.
|
|
jvOutput = jvOutput["result"];
|
|
|
|
// jvOutput may report a server side error.
|
|
// It should report "status".
|
|
}
|
|
else
|
|
{
|
|
// Transport error.
|
|
Json::Value jvRpcError = jvOutput;
|
|
|
|
jvOutput = rpcError(rpcJSON_RPC);
|
|
jvOutput["result"] = jvRpcError;
|
|
}
|
|
|
|
// If had an error, supply invokation in result.
|
|
if (jvOutput.isMember("error"))
|
|
{
|
|
jvOutput["rpc"] = jvRpc; // How the command was seen as method + params.
|
|
jvOutput["request_sent"] = jvRequest; // How the command was translated.
|
|
}
|
|
}
|
|
|
|
if (jvOutput.isMember("error"))
|
|
{
|
|
jvOutput["status"] = "error";
|
|
|
|
nRet = jvOutput.isMember("error_code")
|
|
? lexical_cast_s<int>(jvOutput["error_code"].asString())
|
|
: 1;
|
|
}
|
|
|
|
// YYY We could have a command line flag for single line output for scripts.
|
|
// YYY We would intercept output here and simplify it.
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
jvOutput = rpcError(rpcINTERNAL);
|
|
jvOutput["error_what"] = e.what();
|
|
nRet = rpcINTERNAL;
|
|
}
|
|
catch (...)
|
|
{
|
|
jvOutput = rpcError(rpcINTERNAL);
|
|
jvOutput["error_what"] = "exception";
|
|
nRet = rpcINTERNAL;
|
|
}
|
|
|
|
std::cout << jvOutput.toStyledString();
|
|
|
|
return nRet;
|
|
}
|
|
|
|
Json::Value callRPC(const std::string& strIp, const int iPort, const std::string& strUsername, const std::string& strPassword, const std::string& strPath, const std::string& strMethod, const Json::Value& params)
|
|
{
|
|
// Connect to localhost
|
|
if (!theConfig.QUIET)
|
|
std::cerr << "Connecting to: " << strIp << ":" << iPort << std::endl;
|
|
|
|
boost::asio::ip::tcp::endpoint
|
|
endpoint(boost::asio::ip::address::from_string(strIp), iPort);
|
|
boost::asio::ip::tcp::iostream stream;
|
|
stream.connect(endpoint);
|
|
if (stream.fail())
|
|
throw std::runtime_error("couldn't connect to server");
|
|
|
|
// HTTP basic authentication
|
|
std::string strUserPass64 = EncodeBase64(strUsername + ":" + strPassword);
|
|
std::map<std::string, std::string> mapRequestHeaders;
|
|
mapRequestHeaders["Authorization"] = std::string("Basic ") + strUserPass64;
|
|
|
|
// Send request
|
|
std::string strRequest = JSONRPCRequest(strMethod, params, Json::Value(1));
|
|
cLog(lsDEBUG) << "send request " << strMethod << " : " << strRequest << std::endl;
|
|
std::string strPost = createHTTPPost(strPath, strRequest, mapRequestHeaders);
|
|
stream << strPost << std::flush;
|
|
|
|
// std::cerr << "post " << strPost << std::endl;
|
|
|
|
// Receive reply
|
|
std::map<std::string, std::string> mapHeaders;
|
|
std::string strReply;
|
|
int nStatus = ReadHTTP(stream, mapHeaders, strReply);
|
|
if (nStatus == 401)
|
|
throw std::runtime_error("incorrect rpcuser or rpcpassword (authorization failed)");
|
|
else if ((nStatus >= 400) && (nStatus != 400) && (nStatus != 404) && (nStatus != 500)) // ?
|
|
throw std::runtime_error(strprintf("server returned HTTP error %d", nStatus));
|
|
else if (strReply.empty())
|
|
throw std::runtime_error("no response from server");
|
|
|
|
// Parse reply
|
|
cLog(lsDEBUG) << "RPC reply: " << strReply << std::endl;
|
|
|
|
Json::Reader reader;
|
|
Json::Value valReply;
|
|
|
|
if (!reader.parse(strReply, valReply))
|
|
throw std::runtime_error("couldn't parse reply from server");
|
|
|
|
if (valReply.isNull())
|
|
throw std::runtime_error("expected reply to have result, error and id properties");
|
|
|
|
return valReply;
|
|
}
|
|
|
|
// vim:ts=4
|