20 #include <ripple/rpc/ServerHandler.h>
22 #include <ripple/app/main/Application.h>
23 #include <ripple/app/misc/NetworkOPs.h>
24 #include <ripple/basics/Log.h>
25 #include <ripple/basics/base64.h>
26 #include <ripple/basics/contract.h>
27 #include <ripple/basics/make_SSLContext.h>
28 #include <ripple/beast/net/IPAddressConversion.h>
29 #include <ripple/beast/rfc2616.h>
30 #include <ripple/core/JobQueue.h>
31 #include <ripple/json/json_reader.h>
32 #include <ripple/json/to_string.h>
33 #include <ripple/net/RPCErr.h>
34 #include <ripple/overlay/Overlay.h>
35 #include <ripple/protocol/ErrorCodes.h>
36 #include <ripple/resource/Fees.h>
37 #include <ripple/resource/ResourceManager.h>
38 #include <ripple/rpc/RPCHandler.h>
39 #include <ripple/rpc/Role.h>
40 #include <ripple/rpc/impl/RPCHelpers.h>
41 #include <ripple/rpc/impl/Tuning.h>
42 #include <ripple/rpc/json_body.h>
43 #include <ripple/server/Server.h>
44 #include <ripple/server/SimpleWriter.h>
45 #include <ripple/server/impl/JSONRPCUtil.h>
46 #include <boost/algorithm/string.hpp>
47 #include <boost/beast/http/fields.hpp>
48 #include <boost/beast/http/string_body.hpp>
49 #include <boost/type_traits.hpp>
59 return request.version() >= 11 && request.target() ==
"/" &&
60 request.body().size() == 0 &&
61 request.method() == boost::beast::http::verb::get;
67 boost::beast::http::status status)
69 using namespace boost::beast::http;
71 response<string_body> msg;
72 msg.version(request.version());
75 msg.insert(
"Content-Type",
"text/html");
76 msg.insert(
"Connection",
"close");
77 msg.body() =
"Invalid protocol.";
78 msg.prepare_payload();
79 handoff.
response = std::make_shared<SimpleWriter>(msg);
90 auto const it = h.
find(
"authorization");
91 if ((it == h.
end()) || (it->second.substr(0, 6) !=
"Basic "))
94 boost::trim(strUserPass64);
96 std::string::size_type nColon = strUserPass.
find(
":");
97 if (nColon == std::string::npos)
101 return strUser == port.
user && strPassword == port.
password;
107 boost::asio::io_service& io_service,
113 , m_resourceManager(resourceManager)
114 , m_journal(app_.journal(
"Server"))
115 , m_networkOPs(networkOPs)
116 , m_server(
make_Server(*this, io_service, app_.journal(
"Server")))
117 , m_jobQueue(jobQueue)
119 auto const& group(cm.
group(
"rpc"));
154 boost::asio::ip::tcp::endpoint endpoint)
156 auto const& port = session.
port();
158 auto const c = [
this, &port]() {
163 if (port.limit && c >= port.limit)
166 << port.name <<
" is full; dropping " << endpoint;
178 boost::asio::ip::tcp::endpoint
const& remote_address)
180 using namespace boost::beast;
183 p.
count(
"ws") > 0 || p.count(
"ws2") > 0 || p.count(
"wss") > 0 ||
184 p.count(
"wss2") > 0};
186 if (websocket::is_upgrade(request))
199 <<
"Exception upgrading websocket: " << e.
what() <<
"\n";
201 request, http::status::internal_server_error);
205 auto const beast_remote_address =
209 beast_remote_address,
214 beast_remote_address,
217 is->forwarded_for());
218 ws->appDefined = std::move(is);
222 handoff.
moved =
true;
226 if (bundle && p.count(
"peer") > 0)
228 std::move(bundle), std::move(request), remote_address);
240 return [&](boost::beast::string_view
const& b) {
241 session.
write(b.data(), b.size());
249 for (
auto const& e : h)
253 return std::tolower(static_cast<unsigned char>(kc));
260 template <
class ConstBufferSequence>
264 using boost::asio::buffer_cast;
265 using boost::asio::buffer_size;
271 s.
append(buffer_cast<char const*>(b), buffer_size(b));
302 if (postResult ==
nullptr)
307 "Service Unavailable",
310 detachedSession->close(
true);
321 auto const size = boost::asio::buffer_size(buffers);
326 jvResult[jss::type] = jss::error;
327 jvResult[jss::error] =
"jsonInvalid";
329 boost::beast::multi_buffer sb;
330 Json::stream(jvResult, [&sb](
auto const p,
auto const n) {
331 sb.commit(boost::asio::buffer_copy(
332 sb.prepare(n), boost::asio::buffer(p, n)));
334 JLOG(
m_journal.
trace()) <<
"Websocket sending '" << jvResult <<
"'";
346 [
this, session, jv = std::move(jv)](
350 auto const n = s.length();
351 boost::beast::multi_buffer sb(n);
352 sb.commit(boost::asio::buffer_copy(
353 sb.prepare(n), boost::asio::buffer(s.c_str(), n)));
358 if (postResult ==
nullptr)
361 session->close({boost::beast::websocket::going_away,
"Shutting Down"});
389 using namespace std::chrono_literals;
390 auto const level = (duration >= 10s)
392 : (duration >= 1s) ? journal.
warn() : journal.
debug();
394 JLOG(level) <<
"RPC request processing duration = "
395 << std::chrono::duration_cast<std::chrono::microseconds>(
398 <<
" microseconds. request = " << request;
407 auto is = std::static_pointer_cast<WSInfoSub>(session->appDefined);
408 if (is->getConsumer().disconnect(
m_journal))
411 {boost::beast::websocket::policy_error,
"threshold exceeded"});
431 jr[jss::type] = jss::response;
432 jr[jss::status] = jss::error;
434 ? jss::invalid_API_version
435 : jss::missingCommand;
436 jr[jss::request] = jv;
438 jr[jss::id] = jv[jss::id];
440 jr[jss::jsonrpc] = jv[jss::jsonrpc];
442 jr[jss::ripplerpc] = jv[jss::ripplerpc];
444 jr[jss::api_version] = jv[jss::api_version];
480 {is->user(), is->forwarded_for()}};
492 <<
"Exception while processing WS: " << ex.
what() <<
"\n"
496 is->getConsumer().charge(loadType);
497 if (is->getConsumer().warn())
498 jr[jss::warning] = jss::load;
505 if (jr[jss::result].isMember(jss::error))
507 jr = jr[jss::result];
508 jr[jss::status] = jss::error;
514 if (rq.isMember(jss::passphrase.c_str()))
515 rq[jss::passphrase.c_str()] =
"<masked>";
516 if (rq.isMember(jss::secret.c_str()))
517 rq[jss::secret.c_str()] =
"<masked>";
518 if (rq.isMember(jss::seed.c_str()))
519 rq[jss::seed.c_str()] =
"<masked>";
520 if (rq.isMember(jss::seed_hex.c_str()))
521 rq[jss::seed_hex.c_str()] =
"<masked>";
524 jr[jss::request] = rq;
528 if (jr[jss::result].isMember(
"forwarded") &&
529 jr[jss::result][
"forwarded"])
530 jr = jr[jss::result];
531 jr[jss::status] = jss::success;
535 jr[jss::id] = jv[jss::id];
537 jr[jss::jsonrpc] = jv[jss::jsonrpc];
539 jr[jss::ripplerpc] = jv[jss::ripplerpc];
541 jr[jss::api_version] = jv[jss::api_version];
543 jr[jss::type] = jss::response;
556 session->remoteAddress().at_port(0),
561 auto const iter = session->request().find(
"X-User");
562 if (iter != session->request().end())
563 return iter->value();
564 return boost::beast::string_view{};
570 session->close(
true);
578 sub[
"message"] = std::move(message);
597 boost::string_view user)
605 !reader.
parse(request, jsonOrig) || !jsonOrig ||
619 if (jsonOrig.
isMember(jss::method) && jsonOrig[jss::method] ==
"batch")
622 if (!jsonOrig.
isMember(jss::params) || !jsonOrig[jss::params].
isArray())
624 HTTPReply(400,
"Malformed batch request", output, rpcJ);
627 size = jsonOrig[jss::params].
size();
632 for (
unsigned i = 0; i < size; ++i)
635 batch ? jsonOrig[jss::params][i] : jsonOrig;
640 r[jss::request] = jsonRPC;
649 jsonRPC[jss::params].
size() > 0 &&
650 jsonRPC[jss::params][0u].
isObject())
668 HTTPReply(400, jss::invalid_API_version.c_str(), output, rpcJ);
672 r[jss::request] = jsonRPC;
689 jsonRPC[jss::params].
size() > 0 &&
718 HTTPReply(503,
"Server is overloaded", output, rpcJ);
734 HTTPReply(403,
"Forbidden", output, rpcJ);
743 if (!jsonRPC.
isMember(jss::method) || jsonRPC[jss::method].
isNull())
748 HTTPReply(400,
"Null method", output, rpcJ);
763 HTTPReply(400,
"method is not string", output, rpcJ);
774 if (strMethod.
empty())
779 HTTPReply(400,
"method is empty", output, rpcJ);
798 params = jsonRPC[jss::params];
805 HTTPReply(400,
"params unparseable", output, rpcJ);
810 params = std::move(params[0u]);
814 HTTPReply(400,
"params unparseable", output, rpcJ);
825 if (params.
isMember(jss::ripplerpc))
827 if (!params[jss::ripplerpc].isString())
832 HTTPReply(400,
"ripplerpc is not a string", output, rpcJ);
842 ripplerpc = params[jss::ripplerpc].
asString();
858 params[jss::command] = strMethod;
860 <<
"doRpcCommand:" << strMethod <<
":" << params;
889 <<
" when processing request: "
899 result[jss::warning] = jss::load;
902 if (ripplerpc >=
"2.0")
906 result[jss::status] = jss::error;
907 result[
"code"] = result[jss::error_code];
908 result[
"message"] = result[jss::error_message];
911 <<
": " << result[jss::error_message];
912 r[jss::error] = std::move(result);
916 result[jss::status] = jss::success;
917 r[jss::result] = std::move(result);
930 if (rq.isMember(jss::passphrase.c_str()))
931 rq[jss::passphrase.c_str()] =
"<masked>";
932 if (rq.isMember(jss::secret.c_str()))
933 rq[jss::secret.c_str()] =
"<masked>";
934 if (rq.isMember(jss::seed.c_str()))
935 rq[jss::seed.c_str()] =
"<masked>";
936 if (rq.isMember(jss::seed_hex.c_str()))
937 rq[jss::seed_hex.c_str()] =
"<masked>";
940 result[jss::status] = jss::error;
941 result[jss::request] = rq;
944 <<
": " << result[jss::error_message];
948 result[jss::status] = jss::success;
950 r[jss::result] = std::move(result);
953 if (params.isMember(jss::jsonrpc))
954 r[jss::jsonrpc] = params[jss::jsonrpc];
955 if (params.isMember(jss::ripplerpc))
956 r[jss::ripplerpc] = params[jss::ripplerpc];
957 if (params.isMember(jss::id))
958 r[jss::id] = params[jss::id];
960 reply.
append(std::move(r));
962 reply = std::move(r);
965 reply[jss::result].
isMember(jss::result))
967 reply = reply[jss::result];
970 reply[jss::result][jss::status] = reply[jss::status];
977 int const httpStatus = [&reply]() {
980 if (reply.
isMember(jss::ripplerpc) &&
982 reply[jss::ripplerpc].
asString() >=
"3.0")
986 reply[jss::error].
isMember(jss::error_code) &&
987 reply[jss::error][jss::error_code].
isInt())
989 int const errCode = reply[jss::error][jss::error_code].
asInt();
1000 rpc_time_.
notify(std::chrono::duration_cast<std::chrono::milliseconds>(
1009 static const int maxSize = 10000;
1010 if (response.size() <= maxSize)
1011 stream <<
"Reply: " << response;
1013 stream <<
"Reply: " << response.substr(0, maxSize);
1016 HTTPReply(httpStatus, response, output, rpcJ);
1028 using namespace boost::beast::http;
1030 response<string_body> msg;
1034 msg.result(boost::beast::http::status::ok);
1035 msg.body() =
"<!DOCTYPE html><html><head><title>" +
systemName() +
1036 " Test page for rippled</title></head><body><h1>" +
systemName() +
1037 " Test</h1><p>This page shows rippled http(s) "
1038 "connectivity is working.</p></body></html>";
1042 msg.result(boost::beast::http::status::internal_server_error);
1043 msg.body() =
"<HTML><BODY>Server cannot accept clients: " + reason +
1046 msg.version(request.version());
1048 msg.insert(
"Content-Type",
"text/html");
1049 msg.insert(
"Connection",
"close");
1050 msg.prepare_payload();
1051 handoff.
response = std::make_shared<SimpleWriter>(msg);
1060 for (
auto& p : ports)
1064 if (p.ssl_key.empty() && p.ssl_cert.empty() && p.ssl_chain.empty())
1068 p.ssl_key, p.ssl_cert, p.ssl_chain, p.ssl_ciphers);
1072 p.context = std::make_shared<boost::asio::ssl::context>(
1073 boost::asio::ssl::context::sslv23);
1086 log <<
"Missing 'ip' in [" << p.
name <<
"]";
1087 Throw<std::exception>();
1093 log <<
"Missing 'port' in [" << p.
name <<
"]";
1094 Throw<std::exception>();
1096 else if (*parsed.
port == 0)
1098 log <<
"Port " << *parsed.
port <<
"in [" << p.
name <<
"] is invalid";
1099 Throw<std::exception>();
1105 log <<
"Missing 'protocol' in [" << p.
name <<
"]";
1106 Throw<std::exception>();
1134 if (!config.
exists(
"server"))
1136 log <<
"Required section [server] is missing";
1137 Throw<std::exception>();
1145 for (
auto const& name : names)
1147 if (!config.
exists(name))
1149 log <<
"Missing section: [" << name <<
"]";
1150 Throw<std::exception>();
1160 auto it = result.
begin();
1162 while (it != result.
end())
1164 auto& p = it->protocol;
1168 if (p.erase(
"peer") && p.empty())
1169 it = result.
erase(it);
1178 return p.protocol.count(
"peer") != 0;
1183 log <<
"Error: More than one peer protocol configured in [server]";
1184 Throw<std::exception>();
1188 log <<
"Warning: No peer protocol configured";
1200 if (iter->protocol.count(
"http") > 0 ||
1201 iter->protocol.count(
"https") > 0)
1209 (iter->ip.is_v6() ?
"::1" :
"127.0.0.1")
1210 : iter->ip.to_string();
1224 return port.protocol.count(
"peer") != 0;
1235 ServerHandler::Setup
1250 boost::asio::io_service& io_service,
1256 return std::make_unique<ServerHandler>(
virtual Consumer newInboundEndpoint(beast::IP::Endpoint const &address)=0
Create a new endpoint keyed by inbound IP address or the forwarded IP if proxied.
Provides server functionality for clients.
constexpr unsigned int apiInvalidVersion
API version numbers used in later API versions.
std::uint16_t ws_queue_limit
constexpr unsigned int apiVersionIfUnspecified
std::unique_ptr< Server > make_Server(Handler &handler, boost::asio::io_service &io_service, beast::Journal journal)
Create the HTTP server using the specified handler.
bool warn()
Returns true if the consumer should be warned.
virtual Handoff onHandoff(std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint remote_address)=0
Conditionally accept an incoming HTTP request.
bool disconnect(beast::Journal const &j)
Returns true if the consumer should be disconnected.
static Json::Output makeOutput(Session &session)
std::shared_ptr< Coro > postCoro(JobType t, std::string const &name, F &&f)
Creates a coroutine and adds a job to the queue which will run it.
Json::Value rpcError(int iError)
Handoff statusResponse(http_request_type const &request) const
virtual std::shared_ptr< WSSession > websocketUpgrade()=0
Convert the connection to WebSocket.
Stream trace() const
Severity stream access functions.
void stream(Json::Value const &jv, Write const &write)
Stream compact JSON to the specified function.
Handoff onHandoff(Session &session, std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint const &remote_address)
std::string admin_password
std::vector< boost::asio::ip::network_v6 > admin_nets_v6
@ arrayValue
array value (ordered list)
static Json::Value make_json_error(Json::Int code, Json::Value &&message)
Role roleRequired(unsigned int version, bool betaEnabled, std::string const &method)
Decorator for streaming out compact json.
unsigned int getAPIVersionNumber(Json::Value const &jv, bool betaEnabled)
Retrieve the api version number from the json value.
std::shared_ptr< boost::asio::ssl::context > make_SSLContext(std::string const &cipherList)
Create a self-signed SSL context that allows anonymous Diffie Hellman.
Resource::Consumer requestInboundEndpoint(Resource::Manager &manager, beast::IP::Endpoint const &remoteAddress, Role const &role, boost::string_view const &user, boost::string_view const &forwardedFor)
Provides the beast::insight::Collector service.
std::vector< boost::asio::ip::network_v6 > secure_gateway_nets_v6
std::map< std::reference_wrapper< Port const >, int > count_
boost::asio::ip::address ip
constexpr Json::Int method_not_found
constexpr Json::Int server_overloaded
const Charge feeReferenceRPC
void HTTPReply(int nStatus, std::string const &content, Json::Output const &output, beast::Journal j)
bool isNull() const
isNull() tests to see if this field is null.
void parse_Port(ParsedPort &port, Section const §ion, std::ostream &log)
Resource::Manager & m_resourceManager
Unserialize a JSON document into a Value.
ServerHandler(ServerHandlerCreator const &, Application &app, boost::asio::io_service &io_service, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
Persistent state information for a connection session.
void write(std::string const &s)
Send a copy of data asynchronously.
constexpr Json::Int forbidden
void processRequest(Port const &port, std::string const &request, beast::IP::Endpoint const &remoteIPAddress, Output &&, std::shared_ptr< JobQueue::Coro > coro, boost::string_view forwardedFor, boost::string_view user)
bool onAccept(Session &session, boost::asio::ip::tcp::endpoint endpoint)
std::set< std::string, boost::beast::iless > protocol
static bool isStatusRequest(http_request_type const &request)
virtual NetworkOPs & getOPs()=0
std::vector< std::string > const & values() const
Returns all the values in the section.
std::shared_ptr< boost::asio::ssl::context > make_SSLContextAuthed(std::string const &keyFile, std::string const &certFile, std::string const &chainFile, std::string const &cipherList)
Create an authenticated SSL context using the specified files.
Json::Value processSession(std::shared_ptr< WSSession > const &session, std::shared_ptr< JobQueue::Coro > const &coro, Json::Value const &jv)
static IP::Endpoint from_asio(boost::asio::ip::address const &address)
ServerHandler::Setup setup_ServerHandler(Config const &config, std::ostream &&log)
virtual beast::insight::Group::ptr const & group(std::string const &name)=0
void onClose(Session &session, boost::system::error_code const &)
bool is_keep_alive(boost::beast::http::message< isRequest, Body, Fields > const &m)
std::vector< boost::asio::ip::network_v6 > secure_gateway_nets_v6
Status doCommand(RPC::JsonContext &context, Json::Value &result)
Execute an RPC command and store the results in a Json::Value.
Overlay::Setup setup_Overlay(BasicConfig const &config)
Value & append(const Value &value)
Append value to array at the end.
static Handoff statusRequestResponse(http_request_type const &request, boost::beast::http::status status)
virtual bool serverOkay(std::string &reason)=0
std::vector< boost::asio::ip::network_v6 > admin_nets_v6
std::shared_ptr< InfoSub > pointer
@ objectValue
object value (collection of name/value pairs).
static bool authorized(Port const &port, std::map< std::string, std::string > const &h)
virtual LedgerMaster & getLedgerMaster()=0
beast::insight::Counter rpc_requests_
std::vector< boost::asio::ip::network_v4 > secure_gateway_nets_v4
virtual Config & config()=0
beast::insight::Event rpc_size_
void logDuration(Json::Value const &request, T const &duration, beast::Journal &journal)
static constexpr int maxRequestSize
boost::string_view forwardedFor(http_request_type const &request)
std::vector< boost::asio::ip::network_v4 > admin_nets_v4
std::string base64_decode(std::string const &data)
UInt size() const
Number of values in array or object.
virtual http_request_type & request()=0
Returns the current HTTP request.
Setup const & setup() const
Endpoint from_asio(boost::asio::ip::address const &address)
Convert to Endpoint.
bool isMember(const char *key) const
Return true if the object has a member named key.
A generic endpoint for log messages.
Role requestRole(Role const &required, Port const &port, Json::Value const ¶ms, beast::IP::Endpoint const &remoteIp, boost::string_view const &user)
Return the allowed privilege role.
Configuration information for a Server listening port.
virtual std::shared_ptr< Session > detach()=0
Detach the session.
const Charge feeInvalidRPC
static Port to_Port(ParsedPort const &parsed, std::ostream &log)
std::vector< Port > ports
boost::beast::websocket::permessage_deflate pmd_options
constexpr Json::Int wrong_version
A pool of threads to perform work.
std::string admin_password
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Tracks load and resource consumption.
std::string const & getFullVersionString()
Full server version string.
int error_code_http_status(error_code_i code)
Returns http status that corresponds to the error code.
friend std::unique_ptr< ServerHandler > make_ServerHandler(Application &app, boost::asio::io_service &, JobQueue &, NetworkOPs &, Resource::Manager &, CollectorManager &cm)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
static std::string buffers_to_string(ConstBufferSequence const &bs)
virtual beast::Journal journal(std::string const &name)=0
NetworkOPs & m_networkOPs
std::optional< boost::asio::ip::address > ip
std::condition_variable condition_
Value removeMember(const char *key)
Remove and return the named member.
virtual Consumer newUnlimitedEndpoint(beast::IP::Endpoint const &address)=0
Create a new unlimited endpoint keyed by forwarded IP.
boost::beast::websocket::permessage_deflate pmd_options
std::optional< std::uint16_t > port
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
An endpoint that consumes resources.
std::uint16_t ws_queue_limit
static void setup_Client(ServerHandler::Setup &setup)
void onRequest(Session &session)
std::string getFormatedErrorMessages() const
Returns a user friendly string that list errors in the parsed document.
virtual Overlay & overlay()=0
virtual Port const & port()=0
Returns the Port settings for this connection.
virtual void close(bool graceful)=0
Close the session.
Used to indicate the result of a server connection handoff.
static std::string const & systemName()
beast::insight::Event rpc_time_
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
A version-independent IP address and port combination.
std::shared_ptr< Writer > response
boost::asio::ip::address ip
void onWSMessage(std::shared_ptr< WSSession > session, std::vector< boost::asio::const_buffer > const &buffers)
static std::map< std::string, std::string > build_map(boost::beast::http::fields const &h)
std::set< std::string, boost::beast::iless > protocol
Disposition charge(Charge const &fee)
Apply a load charge to the consumer.
bool is_unspecified(Address const &addr)
Returns true if the address is unspecified.
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
std::string admin_password
bool isObjectOrNull() const
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
std::unique_ptr< Server > m_server
std::vector< boost::asio::ip::network_v4 > secure_gateway_nets_v4
bool exists(std::string const &name) const
Returns true if a section with the given name exists.
void notify(std::chrono::duration< Rep, Period > const &value) const
Push an event notification.
static std::vector< Port > parse_Ports(Config const &config, std::ostream &log)
Section & section(std::string const &name)
Returns the section with the given name.
std::vector< boost::asio::ip::network_v4 > admin_nets_v4
std::string asString() const
Returns the unquoted string value.