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/resource/Fees.h>
34 #include <ripple/resource/ResourceManager.h>
35 #include <ripple/rpc/RPCHandler.h>
36 #include <ripple/rpc/Role.h>
37 #include <ripple/rpc/ServerHandler.h>
38 #include <ripple/rpc/impl/RPCHelpers.h>
39 #include <ripple/rpc/impl/ServerHandlerImp.h>
40 #include <ripple/rpc/impl/Tuning.h>
41 #include <ripple/rpc/json_body.h>
42 #include <ripple/server/Server.h>
43 #include <ripple/server/SimpleWriter.h>
44 #include <ripple/server/impl/JSONRPCUtil.h>
45 #include <boost/algorithm/string.hpp>
46 #include <boost/beast/http/fields.hpp>
47 #include <boost/beast/http/string_body.hpp>
48 #include <boost/type_traits.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;
105 boost::asio::io_service& io_service,
111 , m_resourceManager(resourceManager)
112 , m_journal(app_.journal(
"Server"))
113 , m_networkOPs(networkOPs)
114 , m_server(
make_Server(*this, io_service, app_.journal(
"Server")))
115 , m_jobQueue(jobQueue)
117 auto const& group(cm.
group(
"rpc"));
152 boost::asio::ip::tcp::endpoint endpoint)
154 auto const& port = session.
port();
156 auto const c = [
this, &port]() {
161 if (port.limit && c >= port.limit)
164 << port.name <<
" is full; dropping " << endpoint;
176 boost::asio::ip::tcp::endpoint
const& remote_address)
178 using namespace boost::beast;
181 p.
count(
"ws") > 0 || p.count(
"ws2") > 0 || p.count(
"wss") > 0 ||
182 p.count(
"wss2") > 0};
184 if (websocket::is_upgrade(request))
197 <<
"Exception upgrading websocket: " << e.
what() <<
"\n";
199 request, http::status::internal_server_error);
203 auto const beast_remote_address =
207 beast_remote_address,
212 beast_remote_address,
215 is->forwarded_for());
216 ws->appDefined = std::move(is);
220 handoff.
moved =
true;
224 if (bundle && p.count(
"peer") > 0)
226 std::move(bundle), std::move(request), remote_address);
238 return [&](boost::beast::string_view
const& b) {
239 session.
write(b.data(), b.size());
247 for (
auto const& e : h)
249 auto key(e.name_string().to_string());
251 return std::tolower(static_cast<unsigned char>(kc));
253 c[key] = e.value().to_string();
258 template <
class ConstBufferSequence>
262 using boost::asio::buffer_cast;
263 using boost::asio::buffer_size;
269 s.
append(buffer_cast<char const*>(b), buffer_size(b));
300 if (postResult ==
nullptr)
305 "Service Unavailable",
308 detachedSession->close(
true);
319 auto const size = boost::asio::buffer_size(buffers);
324 jvResult[jss::type] = jss::error;
325 jvResult[jss::error] =
"jsonInvalid";
327 boost::beast::multi_buffer sb;
328 Json::stream(jvResult, [&sb](
auto const p,
auto const n) {
329 sb.commit(boost::asio::buffer_copy(
330 sb.prepare(n), boost::asio::buffer(p, n)));
332 JLOG(
m_journal.
trace()) <<
"Websocket sending '" << jvResult <<
"'";
344 [
this, session, jv = std::move(jv)](
348 auto const n = s.length();
349 boost::beast::multi_buffer sb(n);
350 sb.commit(boost::asio::buffer_copy(
351 sb.prepare(n), boost::asio::buffer(s.c_str(), n)));
356 if (postResult ==
nullptr)
359 session->close({boost::beast::websocket::going_away,
"Shutting Down"});
387 using namespace std::chrono_literals;
388 auto const level = (duration >= 10s)
390 : (duration >= 1s) ? journal.
warn() : journal.
debug();
392 JLOG(level) <<
"RPC request processing duration = "
393 << std::chrono::duration_cast<std::chrono::microseconds>(
396 <<
" microseconds. request = " << request;
405 auto is = std::static_pointer_cast<WSInfoSub>(session->appDefined);
406 if (is->getConsumer().disconnect(
m_journal))
409 {boost::beast::websocket::policy_error,
"threshold exceeded"});
429 jr[jss::type] = jss::response;
430 jr[jss::status] = jss::error;
432 ? jss::invalid_API_version
433 : jss::missingCommand;
434 jr[jss::request] = jv;
436 jr[jss::id] = jv[jss::id];
438 jr[jss::jsonrpc] = jv[jss::jsonrpc];
440 jr[jss::ripplerpc] = jv[jss::ripplerpc];
442 jr[jss::api_version] = jv[jss::api_version];
478 {is->user(), is->forwarded_for()}};
490 <<
"Exception while processing WS: " << ex.
what() <<
"\n"
494 is->getConsumer().charge(loadType);
495 if (is->getConsumer().warn())
496 jr[jss::warning] = jss::load;
503 if (jr[jss::result].isMember(jss::error))
505 jr = jr[jss::result];
506 jr[jss::status] = jss::error;
512 if (rq.isMember(jss::passphrase.c_str()))
513 rq[jss::passphrase.c_str()] =
"<masked>";
514 if (rq.isMember(jss::secret.c_str()))
515 rq[jss::secret.c_str()] =
"<masked>";
516 if (rq.isMember(jss::seed.c_str()))
517 rq[jss::seed.c_str()] =
"<masked>";
518 if (rq.isMember(jss::seed_hex.c_str()))
519 rq[jss::seed_hex.c_str()] =
"<masked>";
522 jr[jss::request] = rq;
526 if (jr[jss::result].isMember(
"forwarded") &&
527 jr[jss::result][
"forwarded"])
528 jr = jr[jss::result];
529 jr[jss::status] = jss::success;
533 jr[jss::id] = jv[jss::id];
535 jr[jss::jsonrpc] = jv[jss::jsonrpc];
537 jr[jss::ripplerpc] = jv[jss::ripplerpc];
539 jr[jss::api_version] = jv[jss::api_version];
541 jr[jss::type] = jss::response;
554 session->remoteAddress().at_port(0),
559 auto const iter = session->request().find(
"X-User");
560 if (iter != session->request().end())
561 return iter->value();
562 return boost::beast::string_view{};
568 session->close(
true);
576 sub[
"message"] = std::move(message);
595 boost::string_view user)
603 !reader.
parse(request, jsonOrig) || !jsonOrig ||
617 if (jsonOrig.
isMember(jss::method) && jsonOrig[jss::method] ==
"batch")
620 if (!jsonOrig.
isMember(jss::params) || !jsonOrig[jss::params].
isArray())
622 HTTPReply(400,
"Malformed batch request", output, rpcJ);
625 size = jsonOrig[jss::params].
size();
630 for (
unsigned i = 0; i < size; ++i)
633 batch ? jsonOrig[jss::params][i] : jsonOrig;
638 r[jss::request] = jsonRPC;
647 jsonRPC[jss::params].
size() > 0 &&
648 jsonRPC[jss::params][0u].
isObject())
666 HTTPReply(400, jss::invalid_API_version.c_str(), output, rpcJ);
670 r[jss::request] = jsonRPC;
687 jsonRPC[jss::params].
size() > 0 &&
716 HTTPReply(503,
"Server is overloaded", output, rpcJ);
732 HTTPReply(403,
"Forbidden", output, rpcJ);
741 if (!jsonRPC.
isMember(jss::method) || jsonRPC[jss::method].
isNull())
746 HTTPReply(400,
"Null method", output, rpcJ);
761 HTTPReply(400,
"method is not string", output, rpcJ);
772 if (strMethod.
empty())
777 HTTPReply(400,
"method is empty", output, rpcJ);
796 params = jsonRPC[jss::params];
803 HTTPReply(400,
"params unparseable", output, rpcJ);
808 params = std::move(params[0u]);
812 HTTPReply(400,
"params unparseable", output, rpcJ);
823 if (params.
isMember(jss::ripplerpc))
825 if (!params[jss::ripplerpc].isString())
830 HTTPReply(400,
"ripplerpc is not a string", output, rpcJ);
840 ripplerpc = params[jss::ripplerpc].
asString();
856 params[jss::command] = strMethod;
858 <<
"doRpcCommand:" << strMethod <<
":" << params;
883 result[jss::warning] = jss::load;
886 if (ripplerpc >=
"2.0")
890 result[jss::status] = jss::error;
891 result[
"code"] = result[jss::error_code];
892 result[
"message"] = result[jss::error_message];
895 <<
": " << result[jss::error_message];
896 r[jss::error] = std::move(result);
900 result[jss::status] = jss::success;
901 r[jss::result] = std::move(result);
914 if (rq.isMember(jss::passphrase.c_str()))
915 rq[jss::passphrase.c_str()] =
"<masked>";
916 if (rq.isMember(jss::secret.c_str()))
917 rq[jss::secret.c_str()] =
"<masked>";
918 if (rq.isMember(jss::seed.c_str()))
919 rq[jss::seed.c_str()] =
"<masked>";
920 if (rq.isMember(jss::seed_hex.c_str()))
921 rq[jss::seed_hex.c_str()] =
"<masked>";
924 result[jss::status] = jss::error;
925 result[jss::request] = rq;
928 <<
": " << result[jss::error_message];
932 result[jss::status] = jss::success;
934 r[jss::result] = std::move(result);
937 if (params.isMember(jss::jsonrpc))
938 r[jss::jsonrpc] = params[jss::jsonrpc];
939 if (params.isMember(jss::ripplerpc))
940 r[jss::ripplerpc] = params[jss::ripplerpc];
941 if (params.isMember(jss::id))
942 r[jss::id] = params[jss::id];
944 reply.
append(std::move(r));
946 reply = std::move(r);
949 reply[jss::result].
isMember(jss::result))
951 reply = reply[jss::result];
954 reply[jss::result][jss::status] = reply[jss::status];
961 rpc_time_.
notify(std::chrono::duration_cast<std::chrono::milliseconds>(
970 static const int maxSize = 10000;
971 if (response.size() <= maxSize)
972 stream <<
"Reply: " << response;
974 stream <<
"Reply: " << response.substr(0, maxSize);
989 using namespace boost::beast::http;
991 response<string_body> msg;
995 msg.result(boost::beast::http::status::ok);
996 msg.body() =
"<!DOCTYPE html><html><head><title>" +
systemName() +
997 " Test page for rippled</title></head><body><h1>" +
systemName() +
998 " Test</h1><p>This page shows rippled http(s) "
999 "connectivity is working.</p></body></html>";
1003 msg.result(boost::beast::http::status::internal_server_error);
1004 msg.body() =
"<HTML><BODY>Server cannot accept clients: " + reason +
1007 msg.version(request.version());
1009 msg.insert(
"Content-Type",
"text/html");
1010 msg.insert(
"Connection",
"close");
1011 msg.prepare_payload();
1012 handoff.
response = std::make_shared<SimpleWriter>(msg);
1021 for (
auto& p :
ports)
1025 if (p.ssl_key.empty() && p.ssl_cert.empty() && p.ssl_chain.empty())
1029 p.ssl_key, p.ssl_cert, p.ssl_chain, p.ssl_ciphers);
1033 p.context = std::make_shared<boost::asio::ssl::context>(
1034 boost::asio::ssl::context::sslv23);
1047 log <<
"Missing 'ip' in [" << p.
name <<
"]";
1048 Throw<std::exception>();
1054 log <<
"Missing 'port' in [" << p.
name <<
"]";
1055 Throw<std::exception>();
1057 else if (*parsed.
port == 0)
1059 log <<
"Port " << *parsed.
port <<
"in [" << p.
name <<
"] is invalid";
1060 Throw<std::exception>();
1070 log <<
"Missing 'protocol' in [" << p.
name <<
"]";
1071 Throw<std::exception>();
1095 if (!config.
exists(
"server"))
1097 log <<
"Required section [server] is missing";
1098 Throw<std::exception>();
1106 for (
auto const& name : names)
1108 if (!config.
exists(name))
1110 log <<
"Missing section: [" << name <<
"]";
1111 Throw<std::exception>();
1121 auto it = result.
begin();
1123 while (it != result.
end())
1125 auto& p = it->protocol;
1129 if (p.erase(
"peer") && p.empty())
1130 it = result.
erase(it);
1139 return p.protocol.count(
"peer") != 0;
1144 log <<
"Error: More than one peer protocol configured in [server]";
1145 Throw<std::exception>();
1149 log <<
"Warning: No peer protocol configured";
1161 if (iter->protocol.count(
"http") > 0 ||
1162 iter->protocol.count(
"https") > 0)
1170 (iter->ip.is_v6() ?
"::1" :
"127.0.0.1")
1171 : iter->ip.to_string();
1185 return port.protocol.count(
"peer") != 0;
1196 ServerHandler::Setup
1211 boost::asio::io_service& io_service,
1217 return std::make_unique<ServerHandlerImp>(
1218 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::vector< beast::IP::Address > admin_ip
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.
std::optional< std::vector< beast::IP::Address > > admin_ip
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.
@ 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.
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)
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
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
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
std::optional< std::vector< beast::IP::Address > > secure_gateway_ip
bool onAccept(Session &session, boost::asio::ip::tcp::endpoint endpoint)
boost::string_view forwardedFor(http_request_type const &request)
std::string base64_decode(std::string const &data)
UInt size() const
Number of values in array or object.
std::vector< beast::IP::Address > secure_gateway_ip
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)
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.
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::string asString() const
Returns the unquoted string value.