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;
887 <<
" when processing request: "
897 result[jss::warning] = jss::load;
900 if (ripplerpc >=
"2.0")
904 result[jss::status] = jss::error;
905 result[
"code"] = result[jss::error_code];
906 result[
"message"] = result[jss::error_message];
909 <<
": " << result[jss::error_message];
910 r[jss::error] = std::move(result);
914 result[jss::status] = jss::success;
915 r[jss::result] = std::move(result);
928 if (rq.isMember(jss::passphrase.c_str()))
929 rq[jss::passphrase.c_str()] =
"<masked>";
930 if (rq.isMember(jss::secret.c_str()))
931 rq[jss::secret.c_str()] =
"<masked>";
932 if (rq.isMember(jss::seed.c_str()))
933 rq[jss::seed.c_str()] =
"<masked>";
934 if (rq.isMember(jss::seed_hex.c_str()))
935 rq[jss::seed_hex.c_str()] =
"<masked>";
938 result[jss::status] = jss::error;
939 result[jss::request] = rq;
942 <<
": " << result[jss::error_message];
946 result[jss::status] = jss::success;
948 r[jss::result] = std::move(result);
951 if (params.isMember(jss::jsonrpc))
952 r[jss::jsonrpc] = params[jss::jsonrpc];
953 if (params.isMember(jss::ripplerpc))
954 r[jss::ripplerpc] = params[jss::ripplerpc];
955 if (params.isMember(jss::id))
956 r[jss::id] = params[jss::id];
958 reply.
append(std::move(r));
960 reply = std::move(r);
963 reply[jss::result].
isMember(jss::result))
965 reply = reply[jss::result];
968 reply[jss::result][jss::status] = reply[jss::status];
975 rpc_time_.
notify(std::chrono::duration_cast<std::chrono::milliseconds>(
984 static const int maxSize = 10000;
985 if (response.size() <= maxSize)
986 stream <<
"Reply: " << response;
988 stream <<
"Reply: " << response.substr(0, maxSize);
1003 using namespace boost::beast::http;
1005 response<string_body> msg;
1009 msg.result(boost::beast::http::status::ok);
1010 msg.body() =
"<!DOCTYPE html><html><head><title>" +
systemName() +
1011 " Test page for rippled</title></head><body><h1>" +
systemName() +
1012 " Test</h1><p>This page shows rippled http(s) "
1013 "connectivity is working.</p></body></html>";
1017 msg.result(boost::beast::http::status::internal_server_error);
1018 msg.body() =
"<HTML><BODY>Server cannot accept clients: " + reason +
1021 msg.version(request.version());
1023 msg.insert(
"Content-Type",
"text/html");
1024 msg.insert(
"Connection",
"close");
1025 msg.prepare_payload();
1026 handoff.
response = std::make_shared<SimpleWriter>(msg);
1035 for (
auto& p :
ports)
1039 if (p.ssl_key.empty() && p.ssl_cert.empty() && p.ssl_chain.empty())
1043 p.ssl_key, p.ssl_cert, p.ssl_chain, p.ssl_ciphers);
1047 p.context = std::make_shared<boost::asio::ssl::context>(
1048 boost::asio::ssl::context::sslv23);
1061 log <<
"Missing 'ip' in [" << p.
name <<
"]";
1062 Throw<std::exception>();
1068 log <<
"Missing 'port' in [" << p.
name <<
"]";
1069 Throw<std::exception>();
1071 else if (*parsed.
port == 0)
1073 log <<
"Port " << *parsed.
port <<
"in [" << p.
name <<
"] is invalid";
1074 Throw<std::exception>();
1080 log <<
"Missing 'protocol' in [" << p.
name <<
"]";
1081 Throw<std::exception>();
1109 if (!config.
exists(
"server"))
1111 log <<
"Required section [server] is missing";
1112 Throw<std::exception>();
1120 for (
auto const& name : names)
1122 if (!config.
exists(name))
1124 log <<
"Missing section: [" << name <<
"]";
1125 Throw<std::exception>();
1135 auto it = result.
begin();
1137 while (it != result.
end())
1139 auto& p = it->protocol;
1143 if (p.erase(
"peer") && p.empty())
1144 it = result.
erase(it);
1153 return p.protocol.count(
"peer") != 0;
1158 log <<
"Error: More than one peer protocol configured in [server]";
1159 Throw<std::exception>();
1163 log <<
"Warning: No peer protocol configured";
1175 if (iter->protocol.count(
"http") > 0 ||
1176 iter->protocol.count(
"https") > 0)
1184 (iter->ip.is_v6() ?
"::1" :
"127.0.0.1")
1185 : iter->ip.to_string();
1199 return port.protocol.count(
"peer") != 0;
1210 ServerHandler::Setup
1225 boost::asio::io_service& io_service,
1231 return std::make_unique<ServerHandlerImp>(
1232 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)
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.