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"});
386 auto is = std::static_pointer_cast<WSInfoSub>(session->appDefined);
387 if (is->getConsumer().disconnect())
390 {boost::beast::websocket::policy_error,
"threshold exceeded"});
410 jr[jss::type] = jss::response;
411 jr[jss::status] = jss::error;
413 ? jss::invalid_API_version
414 : jss::missingCommand;
415 jr[jss::request] = jv;
417 jr[jss::id] = jv[jss::id];
419 jr[jss::jsonrpc] = jv[jss::jsonrpc];
421 jr[jss::ripplerpc] = jv[jss::ripplerpc];
423 jr[jss::api_version] = jv[jss::api_version];
459 {is->user(), is->forwarded_for()}};
468 <<
"Exception while processing WS: " << ex.
what() <<
"\n"
472 is->getConsumer().charge(loadType);
473 if (is->getConsumer().warn())
474 jr[jss::warning] = jss::load;
481 if (jr[jss::result].isMember(jss::error))
483 jr = jr[jss::result];
484 jr[jss::status] = jss::error;
490 if (rq.isMember(jss::passphrase.c_str()))
491 rq[jss::passphrase.c_str()] =
"<masked>";
492 if (rq.isMember(jss::secret.c_str()))
493 rq[jss::secret.c_str()] =
"<masked>";
494 if (rq.isMember(jss::seed.c_str()))
495 rq[jss::seed.c_str()] =
"<masked>";
496 if (rq.isMember(jss::seed_hex.c_str()))
497 rq[jss::seed_hex.c_str()] =
"<masked>";
500 jr[jss::request] = rq;
504 if (jr[jss::result].isMember(
"forwarded") &&
505 jr[jss::result][
"forwarded"])
506 jr = jr[jss::result];
507 jr[jss::status] = jss::success;
511 jr[jss::id] = jv[jss::id];
513 jr[jss::jsonrpc] = jv[jss::jsonrpc];
515 jr[jss::ripplerpc] = jv[jss::ripplerpc];
517 jr[jss::api_version] = jv[jss::api_version];
519 jr[jss::type] = jss::response;
532 session->remoteAddress().at_port(0),
537 auto const iter = session->request().find(
"X-User");
538 if (iter != session->request().end())
539 return iter->value();
540 return boost::beast::string_view{};
546 session->close(
true);
554 sub[
"message"] = std::move(message);
573 boost::string_view user)
581 !reader.
parse(request, jsonOrig) || !jsonOrig ||
595 if (jsonOrig.
isMember(jss::method) && jsonOrig[jss::method] ==
"batch")
598 if (!jsonOrig.
isMember(jss::params) || !jsonOrig[jss::params].
isArray())
600 HTTPReply(400,
"Malformed batch request", output, rpcJ);
603 size = jsonOrig[jss::params].
size();
608 for (
unsigned i = 0; i < size; ++i)
611 batch ? jsonOrig[jss::params][i] : jsonOrig;
616 r[jss::request] = jsonRPC;
625 jsonRPC[jss::params].
size() > 0 &&
626 jsonRPC[jss::params][0u].
isObject())
644 HTTPReply(400, jss::invalid_API_version.c_str(), output, rpcJ);
648 r[jss::request] = jsonRPC;
665 jsonRPC[jss::params].
size() > 0 &&
694 HTTPReply(503,
"Server is overloaded", output, rpcJ);
710 HTTPReply(403,
"Forbidden", output, rpcJ);
719 if (!jsonRPC.
isMember(jss::method) || jsonRPC[jss::method].
isNull())
724 HTTPReply(400,
"Null method", output, rpcJ);
739 HTTPReply(400,
"method is not string", output, rpcJ);
750 if (strMethod.
empty())
755 HTTPReply(400,
"method is empty", output, rpcJ);
774 params = jsonRPC[jss::params];
781 HTTPReply(400,
"params unparseable", output, rpcJ);
786 params = std::move(params[0u]);
790 HTTPReply(400,
"params unparseable", output, rpcJ);
801 if (params.
isMember(jss::ripplerpc))
803 if (!params[jss::ripplerpc].isString())
808 HTTPReply(400,
"ripplerpc is not a string", output, rpcJ);
818 ripplerpc = params[jss::ripplerpc].
asString();
834 params[jss::command] = strMethod;
836 <<
"doRpcCommand:" << strMethod <<
":" << params;
857 result[jss::warning] = jss::load;
860 if (ripplerpc >=
"2.0")
864 result[jss::status] = jss::error;
865 result[
"code"] = result[jss::error_code];
866 result[
"message"] = result[jss::error_message];
869 <<
": " << result[jss::error_message];
870 r[jss::error] = std::move(result);
874 result[jss::status] = jss::success;
875 r[jss::result] = std::move(result);
888 if (rq.isMember(jss::passphrase.c_str()))
889 rq[jss::passphrase.c_str()] =
"<masked>";
890 if (rq.isMember(jss::secret.c_str()))
891 rq[jss::secret.c_str()] =
"<masked>";
892 if (rq.isMember(jss::seed.c_str()))
893 rq[jss::seed.c_str()] =
"<masked>";
894 if (rq.isMember(jss::seed_hex.c_str()))
895 rq[jss::seed_hex.c_str()] =
"<masked>";
898 result[jss::status] = jss::error;
899 result[jss::request] = rq;
902 <<
": " << result[jss::error_message];
906 result[jss::status] = jss::success;
908 r[jss::result] = std::move(result);
911 if (params.isMember(jss::jsonrpc))
912 r[jss::jsonrpc] = params[jss::jsonrpc];
913 if (params.isMember(jss::ripplerpc))
914 r[jss::ripplerpc] = params[jss::ripplerpc];
915 if (params.isMember(jss::id))
916 r[jss::id] = params[jss::id];
918 reply.
append(std::move(r));
920 reply = std::move(r);
923 reply[jss::result].
isMember(jss::result))
925 reply = reply[jss::result];
928 reply[jss::result][jss::status] = reply[jss::status];
935 rpc_time_.
notify(std::chrono::duration_cast<std::chrono::milliseconds>(
944 static const int maxSize = 10000;
945 if (response.size() <= maxSize)
946 stream <<
"Reply: " << response;
948 stream <<
"Reply: " << response.substr(0, maxSize);
963 using namespace boost::beast::http;
965 response<string_body> msg;
969 msg.result(boost::beast::http::status::ok);
970 msg.body() =
"<!DOCTYPE html><html><head><title>" +
systemName() +
971 " Test page for rippled</title></head><body><h1>" +
systemName() +
972 " Test</h1><p>This page shows rippled http(s) "
973 "connectivity is working.</p></body></html>";
977 msg.result(boost::beast::http::status::internal_server_error);
978 msg.body() =
"<HTML><BODY>Server cannot accept clients: " + reason +
981 msg.version(request.version());
983 msg.insert(
"Content-Type",
"text/html");
984 msg.insert(
"Connection",
"close");
985 msg.prepare_payload();
986 handoff.
response = std::make_shared<SimpleWriter>(msg);
995 for (
auto& p :
ports)
999 if (p.ssl_key.empty() && p.ssl_cert.empty() && p.ssl_chain.empty())
1003 p.ssl_key, p.ssl_cert, p.ssl_chain, p.ssl_ciphers);
1007 p.context = std::make_shared<boost::asio::ssl::context>(
1008 boost::asio::ssl::context::sslv23);
1021 log <<
"Missing 'ip' in [" << p.
name <<
"]";
1022 Throw<std::exception>();
1028 log <<
"Missing 'port' in [" << p.
name <<
"]";
1029 Throw<std::exception>();
1031 else if (*parsed.
port == 0)
1033 log <<
"Port " << *parsed.
port <<
"in [" << p.
name <<
"] is invalid";
1034 Throw<std::exception>();
1044 log <<
"Missing 'protocol' in [" << p.
name <<
"]";
1045 Throw<std::exception>();
1069 if (!config.
exists(
"server"))
1071 log <<
"Required section [server] is missing";
1072 Throw<std::exception>();
1080 for (
auto const& name : names)
1082 if (!config.
exists(name))
1084 log <<
"Missing section: [" << name <<
"]";
1085 Throw<std::exception>();
1095 auto it = result.
begin();
1097 while (it != result.
end())
1099 auto& p = it->protocol;
1103 if (p.erase(
"peer") && p.empty())
1104 it = result.
erase(it);
1113 return p.protocol.count(
"peer") != 0;
1118 log <<
"Error: More than one peer protocol configured in [server]";
1119 Throw<std::exception>();
1123 log <<
"Warning: No peer protocol configured";
1135 if (iter->protocol.count(
"http") > 0 ||
1136 iter->protocol.count(
"https") > 0)
1144 (iter->ip.is_v6() ?
"::1" :
"127.0.0.1")
1145 : iter->ip.to_string();
1159 return port.protocol.count(
"peer") != 0;
1170 ServerHandler::Setup
1185 boost::asio::io_service& io_service,
1191 return std::make_unique<ServerHandlerImp>(
1192 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.
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.
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)
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.
Json::Value rpcError(int iError, Json::Value jvResult)
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
bool disconnect()
Returns true if the consumer should be disconnected.
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.