/* * This file is part of the Flowee project * Copyright (c) 2009-2010 Satoshi Nakamoto * Copyright (c) 2009-2015 The Bitcoin Core developers * Copyright (C) 2017,2019 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "chainparamsbase.h" #include "clientversion.h" #include "rpcclient.h" #include #include "rpcprotocol.h" #include #include "utilstrencodings.h" #include "utiltime.h" #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////////// // // Start // // // Exception thrown on connection error. This error is used to determine // when to wait if -rpcwait is given. // class CConnectionFailed : public std::runtime_error { public: explicit inline CConnectionFailed(const std::string& msg) : std::runtime_error(msg) {} }; static bool AppInitRPC(int argc, char* argv[]) { // // Parameters // Settings::HubCli allowedArgs; try { ParseParameters(argc, argv, allowedArgs); } catch (const std::exception& e) { fprintf(stderr, "Error parsing program options: %s\n", e.what()); return false; } if (argc<2 || mapArgs.count("-?") || mapArgs.count("-h") || mapArgs.count("-help") || mapArgs.count("-version")) { std::string strUsage = _("Flowee RPC client version") + " " + FormatFullVersion() + "\n"; if (!mapArgs.count("-version")) { strUsage += "\n" + _("Usage:") + "\n" + " bitcoin-cli [options] [params] " + _("Send command to the hub") + "\n" + " bitcoin-cli [options] help " + _("List commands") + "\n" + " bitcoin-cli [options] help " + _("Get help for a command") + "\n"; strUsage += "\n" + allowedArgs.helpMessage(); } fprintf(stdout, "%s", strUsage.c_str()); return false; } if (!boost::filesystem::is_directory(GetDataDir(false))) { fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", mapArgs["-datadir"].c_str()); return false; } try { SelectBaseParams(ChainNameFromCommandLine()); } catch (const std::exception& e) { fprintf(stderr, "Error: %s\n", e.what()); return false; } try { ReadConfigFile(mapArgs, mapMultiArgs); } catch (const std::exception& e) { fprintf(stderr,"Error reading configuration file: %s\n", e.what()); return false; } return true; } /** Reply structure for request_done to fill in */ struct HTTPReply { int status; std::string body; }; static void http_request_done(struct evhttp_request *req, void *ctx) { HTTPReply *reply = static_cast(ctx); if (req == NULL) { /* If req is NULL, it means an error occurred while connecting, but * I'm not sure how to find out which one. We also don't really care. */ reply->status = 0; return; } reply->status = evhttp_request_get_response_code(req); struct evbuffer *buf = evhttp_request_get_input_buffer(req); if (buf) { size_t size = evbuffer_get_length(buf); const char *data = (const char*)evbuffer_pullup(buf, size); if (data) reply->body = std::string(data, size); evbuffer_drain(buf, size); } } UniValue CallRPC(const std::string& strMethod, const UniValue& params) { std::string host = GetArg("-rpcconnect", DEFAULT_RPCCONNECT); int port = GetArg("-rpcport", BaseParams().RPCPort()); // Create event base struct event_base *base = event_base_new(); // TODO RAII if (!base) throw std::runtime_error("cannot create event_base"); // Synchronously look up hostname struct evhttp_connection *evcon = evhttp_connection_base_new(base, NULL, host.c_str(), port); // TODO RAII if (evcon == NULL) throw std::runtime_error("create connection failed"); evhttp_connection_set_timeout(evcon, GetArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT)); HTTPReply response; struct evhttp_request *req = evhttp_request_new(http_request_done, (void*)&response); // TODO RAII if (req == NULL) throw std::runtime_error("create http request failed"); // Get credentials std::string strRPCUserColonPass; if (mapArgs["-rpcpassword"] == "") { // Try fall back to cookie-based authentication if no password is provided boost::filesystem::path path = GetAuthCookieFile(); bool found = GetAuthCookie(path, &strRPCUserColonPass) ; #ifndef WIN32 if (!found) // The default place where cookies are stored in our packaging standard found = GetAuthCookie("/etc/flowee/.cookie", &strRPCUserColonPass) ; #endif if (!found) { throw std::runtime_error(strprintf( _("Could not locate RPC credentials. No authentication cookie could be found, and no rpcpassword is set in the configuration file (%s)"), GetConfigFile().string().c_str())); } } else { std::string user = mapArgs["-rpcuser"]; if (user.empty()) user = "__cookie__"; strRPCUserColonPass = user + ":" + mapArgs["-rpcpassword"]; } struct evkeyvalq *output_headers = evhttp_request_get_output_headers(req); assert(output_headers); evhttp_add_header(output_headers, "Host", host.c_str()); evhttp_add_header(output_headers, "Connection", "close"); evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str()); // Attach request data std::string strRequest = JSONRPCRequest(strMethod, params, 1); struct evbuffer * output_buffer = evhttp_request_get_output_buffer(req); assert(output_buffer); evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); int r = evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/"); if (r != 0) { evhttp_connection_free(evcon); event_base_free(base); throw CConnectionFailed("send http request failed"); } event_base_dispatch(base); evhttp_connection_free(evcon); event_base_free(base); if (response.status == 0) throw CConnectionFailed("couldn't connect to server"); else if (response.status == HTTP_UNAUTHORIZED) throw std::runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR) throw std::runtime_error(strprintf("server returned HTTP error %d", response.status)); else if (response.body.empty()) throw std::runtime_error("no response from server"); // Parse reply UniValue valReply(UniValue::VSTR); if (!valReply.read(response.body)) throw std::runtime_error("couldn't parse reply from server"); const UniValue& reply = valReply.get_obj(); if (reply.empty()) throw std::runtime_error("expected reply to have result, error and id properties"); return reply; } int CommandLineRPC(int argc, char *argv[]) { std::string strPrint; int nRet = 0; try { // Skip switches while (argc > 1 && IsSwitchChar(argv[1][0])) { argc--; argv++; } // Method if (argc < 2) throw std::runtime_error("too few parameters"); std::string strMethod = argv[1]; // Parameters default to strings std::vector strParams(&argv[2], &argv[argc]); UniValue params = RPCConvertValues(strMethod, strParams); // Execute and handle connection failures with -rpcwait const bool fWait = GetBoolArg("-rpcwait", false); do { try { const UniValue reply = CallRPC(strMethod, params); // Parse reply const UniValue& result = find_value(reply, "result"); const UniValue& error = find_value(reply, "error"); if (!error.isNull()) { // Error int code = error["code"].get_int(); if (fWait && code == RPC_IN_WARMUP) throw CConnectionFailed("server in warmup"); strPrint = "error: " + error.write(); nRet = abs(code); if (error.isObject()) { UniValue errCode = find_value(error, "code"); UniValue errMsg = find_value(error, "message"); strPrint = errCode.isNull() ? "" : "error code: "+errCode.getValStr()+"\n"; if (errMsg.isStr()) strPrint += "error message:\n"+errMsg.get_str(); } } else { // Result if (result.isNull()) strPrint = ""; else if (result.isStr()) strPrint = result.get_str(); else strPrint = result.write(2); } // Connection succeeded, no need to retry. break; } catch (const CConnectionFailed&) { if (fWait) MilliSleep(1000); else throw; } } while (fWait); } catch (const boost::thread_interrupted&) { throw; } catch (const std::exception& e) { strPrint = std::string("error: ") + e.what(); nRet = EXIT_FAILURE; } catch (...) { PrintExceptionContinue(NULL, "CommandLineRPC()"); throw; } if (strPrint != "") { fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); } return nRet; } int main(int argc, char* argv[]) { SetupEnvironment(); if (!SetupNetworking()) { fprintf(stderr, "Error: Initializing networking failed\n"); exit(1); } try { if(!AppInitRPC(argc, argv)) return EXIT_FAILURE; } catch (const std::exception& e) { PrintExceptionContinue(&e, "AppInitRPC()"); return EXIT_FAILURE; } catch (...) { PrintExceptionContinue(NULL, "AppInitRPC()"); return EXIT_FAILURE; } int ret = EXIT_FAILURE; try { ret = CommandLineRPC(argc, argv); } catch (const std::exception& e) { PrintExceptionContinue(&e, "CommandLineRPC()"); } catch (...) { PrintExceptionContinue(NULL, "CommandLineRPC()"); } return ret; }