20 #include <ripple/app/main/Application.h>
21 #include <ripple/app/misc/NetworkOPs.h>
22 #include <ripple/basics/Log.h>
23 #include <ripple/basics/base64.h>
24 #include <ripple/basics/contract.h>
25 #include <ripple/basics/make_SSLContext.h>
26 #include <ripple/beast/net/IPAddressConversion.h>
27 #include <ripple/beast/rfc2616.h>
28 #include <ripple/core/JobQueue.h>
29 #include <ripple/json/json_reader.h>
30 #include <ripple/json/to_string.h>
31 #include <ripple/net/RPCErr.h>
32 #include <ripple/overlay/Overlay.h>
33 #include <ripple/protocol/ErrorCodes.h>
34 #include <ripple/resource/Fees.h>
35 #include <ripple/resource/ResourceManager.h>
36 #include <ripple/rpc/RPCHandler.h>
37 #include <ripple/rpc/Role.h>
38 #include <ripple/rpc/ServerHandler.h>
39 #include <ripple/rpc/impl/RPCHelpers.h>
40 #include <ripple/rpc/impl/ServerHandlerImp.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;
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)
250 auto key(e.name_string().to_string());
252 return std::tolower(static_cast<unsigned char>(kc));
254 c[key] = e.value().to_string();
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>();
1159 auto it = result.
begin();
1161 while (it != result.
end())
1163 auto& p = it->protocol;
1167 if (p.erase(
"peer") && p.empty())
1168 it = result.
erase(it);
1177 return p.protocol.count(
"peer") != 0;
1182 log <<
"Error: More than one peer protocol configured in [server]";
1183 Throw<std::exception>();
1187 log <<
"Warning: No peer protocol configured";
1199 if (iter->protocol.count(
"http") > 0 ||
1200 iter->protocol.count(
"https") > 0)
1208 (iter->ip.is_v6() ?
"::1" :
"127.0.0.1")
1209 : iter->ip.to_string();
1223 return port.protocol.count(
"peer") != 0;
1234 ServerHandler::Setup
1249 boost::asio::io_service& io_service,
1255 return std::make_unique<ServerHandlerImp>(
1256 app, io_service, jobQueue, networkOPs, resourceManager, cm);
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::map< std::reference_wrapper< Port const >, int > count_
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.
ServerHandlerImp(Application &app, boost::asio::io_service &io_service, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
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)
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.
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
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.
beast::insight::Counter rpc_requests_
void parse_Port(ParsedPort &port, Section const §ion, std::ostream &log)
Unserialize a JSON document into a Value.
Persistent state information for a connection session.
void write(std::string const &s)
Send a copy of data asynchronously.
constexpr Json::Int forbidden
boost::asio::ip::address ip
void onRequest(Session &session)
Handoff statusResponse(http_request_type const &request) const
std::set< std::string, boost::beast::iless > protocol
static bool isStatusRequest(http_request_type const &request)
virtual NetworkOPs & getOPs()=0
void onClose(Session &session, boost::system::error_code const &)
NetworkOPs & m_networkOPs
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.
static IP::Endpoint from_asio(boost::asio::ip::address const &address)
beast::insight::Event rpc_time_
ServerHandler::Setup setup_ServerHandler(Config const &config, std::ostream &&log)
virtual beast::insight::Group::ptr const & group(std::string const &name)=0
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)
std::unique_ptr< ServerHandler > make_ServerHandler(Application &app, boost::asio::io_service &io_service, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
virtual bool serverOkay(std::string &reason)=0
std::vector< boost::asio::ip::network_v6 > admin_nets_v6
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)
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
std::vector< boost::asio::ip::network_v4 > secure_gateway_nets_v4
virtual Config & config()=0
std::condition_variable condition_
Json::Value processSession(std::shared_ptr< WSSession > const &session, std::shared_ptr< JobQueue::Coro > const &coro, Json::Value const &jv)
void logDuration(Json::Value const &request, T const &duration, beast::Journal &journal)
static constexpr int maxRequestSize
bool onAccept(Session &session, boost::asio::ip::tcp::endpoint endpoint)
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.
beast::insight::Event rpc_size_
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.
std::unique_ptr< Server > m_server
const Charge feeInvalidRPC
Resource::Manager & m_resourceManager
static Port to_Port(ParsedPort const &parsed, std::ostream &log)
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.
std::string admin_password
void onWSMessage(std::shared_ptr< WSSession > session, std::vector< boost::asio::const_buffer > const &buffers)
int error_code_http_status(error_code_i code)
Returns http status that corresponds to the error code.
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
std::optional< boost::asio::ip::address > ip
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
Setup const & setup() const
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)
std::string getFormatedErrorMessages() const
Returns a user friendly string that list errors in the parsed document.
virtual Overlay & overlay()=0
std::vector< Port > ports
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()
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
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::vector< boost::asio::ip::network_v4 > secure_gateway_nets_v4
Handoff onHandoff(Session &session, std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint const &remote_address)
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.