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/ConfigSections.h>
31 #include <ripple/core/JobQueue.h>
32 #include <ripple/json/json_reader.h>
33 #include <ripple/json/to_string.h>
34 #include <ripple/net/RPCErr.h>
35 #include <ripple/overlay/Overlay.h>
36 #include <ripple/protocol/ErrorCodes.h>
37 #include <ripple/resource/Fees.h>
38 #include <ripple/resource/ResourceManager.h>
39 #include <ripple/rpc/RPCHandler.h>
40 #include <ripple/rpc/Role.h>
41 #include <ripple/rpc/impl/RPCHelpers.h>
42 #include <ripple/rpc/impl/Tuning.h>
43 #include <ripple/rpc/json_body.h>
44 #include <ripple/server/Server.h>
45 #include <ripple/server/SimpleWriter.h>
46 #include <ripple/server/impl/JSONRPCUtil.h>
47 #include <boost/algorithm/string.hpp>
48 #include <boost/beast/http/fields.hpp>
49 #include <boost/beast/http/string_body.hpp>
58 return request.version() >= 11 && request.target() ==
"/" &&
59 request.body().size() == 0 &&
60 request.method() == boost::beast::http::verb::get;
66 boost::beast::http::status status)
68 using namespace boost::beast::http;
70 response<string_body> msg;
71 msg.version(request.version());
74 msg.insert(
"Content-Type",
"text/html");
75 msg.insert(
"Connection",
"close");
76 msg.body() =
"Invalid protocol.";
77 msg.prepare_payload();
78 handoff.
response = std::make_shared<SimpleWriter>(msg);
89 auto const it = h.
find(
"authorization");
90 if ((it == h.
end()) || (it->second.substr(0, 6) !=
"Basic "))
93 boost::trim(strUserPass64);
95 std::string::size_type nColon = strUserPass.
find(
":");
96 if (nColon == std::string::npos)
100 return strUser == port.
user && strPassword == port.
password;
106 boost::asio::io_service& io_service,
112 , m_resourceManager(resourceManager)
113 , m_journal(app_.journal(
"Server"))
114 , m_networkOPs(networkOPs)
115 , m_server(
make_Server(*this, io_service, app_.journal(
"Server")))
116 , m_jobQueue(jobQueue)
118 auto const& group(cm.
group(
"rpc"));
153 boost::asio::ip::tcp::endpoint endpoint)
155 auto const& port = session.
port();
157 auto const c = [
this, &port]() {
162 if (port.limit && c >= port.limit)
165 << port.name <<
" is full; dropping " << endpoint;
177 boost::asio::ip::tcp::endpoint
const& remote_address)
179 using namespace boost::beast;
182 p.
count(
"ws") > 0 || p.count(
"ws2") > 0 || p.count(
"wss") > 0 ||
183 p.count(
"wss2") > 0};
185 if (websocket::is_upgrade(request))
198 <<
"Exception upgrading websocket: " << e.
what() <<
"\n";
200 request, http::status::internal_server_error);
204 auto const beast_remote_address =
208 beast_remote_address,
213 beast_remote_address,
216 is->forwarded_for());
217 ws->appDefined = std::move(is);
221 handoff.
moved =
true;
225 if (bundle && p.count(
"peer") > 0)
227 std::move(bundle), std::move(request), remote_address);
239 return [&](boost::beast::string_view
const& b) {
240 session.
write(b.data(), b.size());
248 for (
auto const& e : h)
252 return std::tolower(static_cast<unsigned char>(kc));
259 template <
class ConstBufferSequence>
263 using boost::asio::buffer_cast;
264 using boost::asio::buffer_size;
270 s.
append(buffer_cast<char const*>(b), buffer_size(b));
301 if (postResult ==
nullptr)
306 "Service Unavailable",
309 detachedSession->close(
true);
320 auto const size = boost::asio::buffer_size(buffers);
325 jvResult[jss::type] = jss::error;
326 jvResult[jss::error] =
"jsonInvalid";
328 boost::beast::multi_buffer sb;
329 Json::stream(jvResult, [&sb](
auto const p,
auto const n) {
330 sb.commit(boost::asio::buffer_copy(
331 sb.prepare(n), boost::asio::buffer(p, n)));
333 JLOG(
m_journal.
trace()) <<
"Websocket sending '" << jvResult <<
"'";
345 [
this, session, jv = std::move(jv)](
349 auto const n = s.length();
350 boost::beast::multi_buffer sb(n);
351 sb.commit(boost::asio::buffer_copy(
352 sb.prepare(n), boost::asio::buffer(s.c_str(), n)));
357 if (postResult ==
nullptr)
360 session->close({boost::beast::websocket::going_away,
"Shutting Down"});
388 using namespace std::chrono_literals;
389 auto const level = (duration >= 10s)
391 : (duration >= 1s) ? journal.
warn() : journal.
debug();
393 JLOG(level) <<
"RPC request processing duration = "
394 << std::chrono::duration_cast<std::chrono::microseconds>(
397 <<
" microseconds. request = " << request;
406 auto is = std::static_pointer_cast<WSInfoSub>(session->appDefined);
407 if (is->getConsumer().disconnect(
m_journal))
410 {boost::beast::websocket::policy_error,
"threshold exceeded"});
430 jr[jss::type] = jss::response;
431 jr[jss::status] = jss::error;
433 ? jss::invalid_API_version
434 : jss::missingCommand;
435 jr[jss::request] = jv;
437 jr[jss::id] = jv[jss::id];
439 jr[jss::jsonrpc] = jv[jss::jsonrpc];
441 jr[jss::ripplerpc] = jv[jss::ripplerpc];
443 jr[jss::api_version] = jv[jss::api_version];
479 {is->user(), is->forwarded_for()}};
491 <<
"Exception while processing WS: " << ex.
what() <<
"\n"
495 is->getConsumer().charge(loadType);
496 if (is->getConsumer().warn())
497 jr[jss::warning] = jss::load;
504 if (jr[jss::result].isMember(jss::error))
506 jr = jr[jss::result];
507 jr[jss::status] = jss::error;
513 if (rq.isMember(jss::passphrase.c_str()))
514 rq[jss::passphrase.c_str()] =
"<masked>";
515 if (rq.isMember(jss::secret.c_str()))
516 rq[jss::secret.c_str()] =
"<masked>";
517 if (rq.isMember(jss::seed.c_str()))
518 rq[jss::seed.c_str()] =
"<masked>";
519 if (rq.isMember(jss::seed_hex.c_str()))
520 rq[jss::seed_hex.c_str()] =
"<masked>";
523 jr[jss::request] = rq;
527 if (jr[jss::result].isMember(
"forwarded") &&
528 jr[jss::result][
"forwarded"])
529 jr = jr[jss::result];
530 jr[jss::status] = jss::success;
534 jr[jss::id] = jv[jss::id];
536 jr[jss::jsonrpc] = jv[jss::jsonrpc];
538 jr[jss::ripplerpc] = jv[jss::ripplerpc];
540 jr[jss::api_version] = jv[jss::api_version];
542 jr[jss::type] = jss::response;
555 session->remoteAddress().at_port(0),
560 auto const iter = session->request().find(
"X-User");
561 if (iter != session->request().end())
562 return iter->value();
563 return boost::beast::string_view{};
569 session->close(
true);
577 sub[
"message"] = std::move(message);
596 boost::string_view user)
604 !reader.
parse(request, jsonOrig) || !jsonOrig ||
618 if (jsonOrig.
isMember(jss::method) && jsonOrig[jss::method] ==
"batch")
621 if (!jsonOrig.
isMember(jss::params) || !jsonOrig[jss::params].
isArray())
623 HTTPReply(400,
"Malformed batch request", output, rpcJ);
626 size = jsonOrig[jss::params].
size();
631 for (
unsigned i = 0; i < size; ++i)
634 batch ? jsonOrig[jss::params][i] : jsonOrig;
639 r[jss::request] = jsonRPC;
648 jsonRPC[jss::params].
size() > 0 &&
649 jsonRPC[jss::params][0u].
isObject())
667 HTTPReply(400, jss::invalid_API_version.c_str(), output, rpcJ);
671 r[jss::request] = jsonRPC;
688 jsonRPC[jss::params].
size() > 0 &&
717 HTTPReply(503,
"Server is overloaded", output, rpcJ);
733 HTTPReply(403,
"Forbidden", output, rpcJ);
742 if (!jsonRPC.
isMember(jss::method) || jsonRPC[jss::method].
isNull())
747 HTTPReply(400,
"Null method", output, rpcJ);
762 HTTPReply(400,
"method is not string", output, rpcJ);
773 if (strMethod.
empty())
778 HTTPReply(400,
"method is empty", output, rpcJ);
797 params = jsonRPC[jss::params];
804 HTTPReply(400,
"params unparseable", output, rpcJ);
809 params = std::move(params[0u]);
813 HTTPReply(400,
"params unparseable", output, rpcJ);
824 if (params.
isMember(jss::ripplerpc))
826 if (!params[jss::ripplerpc].isString())
831 HTTPReply(400,
"ripplerpc is not a string", output, rpcJ);
841 ripplerpc = params[jss::ripplerpc].
asString();
857 params[jss::command] = strMethod;
859 <<
"doRpcCommand:" << strMethod <<
":" << params;
888 <<
" when processing request: "
898 result[jss::warning] = jss::load;
901 if (ripplerpc >=
"2.0")
905 result[jss::status] = jss::error;
906 result[
"code"] = result[jss::error_code];
907 result[
"message"] = result[jss::error_message];
910 <<
": " << result[jss::error_message];
911 r[jss::error] = std::move(result);
915 result[jss::status] = jss::success;
916 r[jss::result] = std::move(result);
929 if (rq.isMember(jss::passphrase.c_str()))
930 rq[jss::passphrase.c_str()] =
"<masked>";
931 if (rq.isMember(jss::secret.c_str()))
932 rq[jss::secret.c_str()] =
"<masked>";
933 if (rq.isMember(jss::seed.c_str()))
934 rq[jss::seed.c_str()] =
"<masked>";
935 if (rq.isMember(jss::seed_hex.c_str()))
936 rq[jss::seed_hex.c_str()] =
"<masked>";
939 result[jss::status] = jss::error;
940 result[jss::request] = rq;
943 <<
": " << result[jss::error_message];
947 result[jss::status] = jss::success;
949 r[jss::result] = std::move(result);
952 if (params.isMember(jss::jsonrpc))
953 r[jss::jsonrpc] = params[jss::jsonrpc];
954 if (params.isMember(jss::ripplerpc))
955 r[jss::ripplerpc] = params[jss::ripplerpc];
956 if (params.isMember(jss::id))
957 r[jss::id] = params[jss::id];
959 reply.
append(std::move(r));
961 reply = std::move(r);
964 reply[jss::result].
isMember(jss::result))
966 reply = reply[jss::result];
969 reply[jss::result][jss::status] = reply[jss::status];
976 int const httpStatus = [&reply]() {
979 if (reply.
isMember(jss::ripplerpc) &&
981 reply[jss::ripplerpc].
asString() >=
"3.0")
985 reply[jss::error].
isMember(jss::error_code) &&
986 reply[jss::error][jss::error_code].
isInt())
988 int const errCode = reply[jss::error][jss::error_code].
asInt();
999 rpc_time_.
notify(std::chrono::duration_cast<std::chrono::milliseconds>(
1008 static const int maxSize = 10000;
1009 if (response.size() <= maxSize)
1010 stream <<
"Reply: " << response;
1012 stream <<
"Reply: " << response.substr(0, maxSize);
1015 HTTPReply(httpStatus, response, output, rpcJ);
1027 using namespace boost::beast::http;
1029 response<string_body> msg;
1033 msg.result(boost::beast::http::status::ok);
1034 msg.body() =
"<!DOCTYPE html><html><head><title>" +
systemName() +
1035 " Test page for rippled</title></head><body><h1>" +
systemName() +
1036 " Test</h1><p>This page shows rippled http(s) "
1037 "connectivity is working.</p></body></html>";
1041 msg.result(boost::beast::http::status::internal_server_error);
1042 msg.body() =
"<HTML><BODY>Server cannot accept clients: " + reason +
1045 msg.version(request.version());
1047 msg.insert(
"Content-Type",
"text/html");
1048 msg.insert(
"Connection",
"close");
1049 msg.prepare_payload();
1050 handoff.
response = std::make_shared<SimpleWriter>(msg);
1059 for (
auto& p : ports)
1063 if (p.ssl_key.empty() && p.ssl_cert.empty() && p.ssl_chain.empty())
1067 p.ssl_key, p.ssl_cert, p.ssl_chain, p.ssl_ciphers);
1071 p.context = std::make_shared<boost::asio::ssl::context>(
1072 boost::asio::ssl::context::sslv23);
1085 log <<
"Missing 'ip' in [" << p.
name <<
"]";
1086 Throw<std::exception>();
1092 log <<
"Missing 'port' in [" << p.
name <<
"]";
1093 Throw<std::exception>();
1095 else if (*parsed.
port == 0)
1097 log <<
"Port " << *parsed.
port <<
"in [" << p.
name <<
"] is invalid";
1098 Throw<std::exception>();
1104 log <<
"Missing 'protocol' in [" << p.
name <<
"]";
1105 Throw<std::exception>();
1133 if (!config.
exists(
"server"))
1135 log <<
"Required section [server] is missing";
1136 Throw<std::exception>();
1144 for (
auto const& name : names)
1146 if (!config.
exists(name))
1148 log <<
"Missing section: [" << name <<
"]";
1149 Throw<std::exception>();
1154 if (name == SECTION_PORT_GRPC)
1165 auto it = result.
begin();
1167 while (it != result.
end())
1169 auto& p = it->protocol;
1173 if (p.erase(
"peer") && p.empty())
1174 it = result.
erase(it);
1183 return p.protocol.count(
"peer") != 0;
1188 log <<
"Error: More than one peer protocol configured in [server]";
1189 Throw<std::exception>();
1193 log <<
"Warning: No peer protocol configured";
1205 if (iter->protocol.count(
"http") > 0 ||
1206 iter->protocol.count(
"https") > 0)
1214 (iter->ip.is_v6() ?
"::1" :
"127.0.0.1")
1215 : iter->ip.to_string();
1229 return port.protocol.count(
"peer") != 0;
1240 ServerHandler::Setup
1255 boost::asio::io_service& io_service,
1261 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.