20 #include <ripple/app/main/Application.h>
21 #include <ripple/app/misc/NetworkOPs.h>
22 #include <ripple/basics/base64.h>
23 #include <ripple/beast/rfc2616.h>
24 #include <ripple/beast/net/IPAddressConversion.h>
25 #include <ripple/json/json_reader.h>
26 #include <ripple/rpc/json_body.h>
27 #include <ripple/rpc/ServerHandler.h>
28 #include <ripple/server/Server.h>
29 #include <ripple/server/impl/JSONRPCUtil.h>
30 #include <ripple/rpc/impl/ServerHandlerImp.h>
31 #include <ripple/rpc/impl/RPCHelpers.h>
32 #include <ripple/basics/contract.h>
33 #include <ripple/basics/Log.h>
34 #include <ripple/basics/make_SSLContext.h>
35 #include <ripple/core/JobQueue.h>
36 #include <ripple/json/to_string.h>
37 #include <ripple/net/RPCErr.h>
38 #include <ripple/overlay/Overlay.h>
39 #include <ripple/resource/ResourceManager.h>
40 #include <ripple/resource/Fees.h>
41 #include <ripple/rpc/impl/Tuning.h>
42 #include <ripple/rpc/Role.h>
43 #include <ripple/rpc/RPCHandler.h>
44 #include <ripple/server/SimpleWriter.h>
45 #include <boost/beast/http/fields.hpp>
46 #include <boost/beast/http/string_body.hpp>
47 #include <boost/algorithm/string.hpp>
48 #include <boost/type_traits.hpp>
49 #include <boost/optional.hpp>
50 #include <boost/regex.hpp>
62 request.version() >= 11 &&
63 request.target() ==
"/" &&
64 request.body().size() == 0 &&
65 request.method() == boost::beast::http::verb::get;
73 using namespace boost::beast::http;
75 response<string_body> msg;
76 msg.version(request.version());
79 msg.insert(
"Content-Type",
"text/html");
80 msg.insert(
"Connection",
"close");
81 msg.body() =
"Invalid protocol.";
82 msg.prepare_payload();
83 handoff.
response = std::make_shared<SimpleWriter>(msg);
97 auto const it = h.
find (
"authorization");
98 if ((it == h.
end ()) || (it->second.substr (0, 6) !=
"Basic "))
101 boost::trim (strUserPass64);
103 std::string::size_type nColon = strUserPass.
find (
":");
104 if (nColon == std::string::npos)
108 return strUser == port.
user && strPassword == port.
password;
113 boost::asio::io_service& io_service,
JobQueue& jobQueue,
118 , m_resourceManager (resourceManager)
119 , m_journal (app_.journal(
"Server"))
120 , m_networkOPs (networkOPs)
122 *this, io_service, app_.journal(
"Server")))
123 , m_jobQueue (jobQueue)
125 auto const& group (cm.
group (
"rpc"));
155 boost::asio::ip::tcp::endpoint endpoint)
164 session.
port().
name <<
" is full; dropping " <<
177 boost::asio::ip::tcp::endpoint
const& remote_address)
179 using namespace boost::beast;
182 p.
count(
"ws") > 0 || p.count(
"ws2") > 0 ||
183 p.count(
"wss") > 0 || p.count(
"wss2") > 0};
185 if(websocket::is_upgrade(request))
198 <<
"Exception upgrading websocket: " << e.
what() <<
"\n";
200 http::status::internal_server_error);
203 auto is {std::make_shared<WSInfoSub>(
m_networkOPs, ws)};
204 auto const beast_remote_address =
209 beast_remote_address, is->user()),
211 is->forwarded_for());
212 ws->appDefined = std::move(is);
216 handoff.
moved =
true;
220 if (bundle && p.count(
"peer") > 0)
222 std::move(request), remote_address);
234 return [&](boost::beast::string_view
const& b)
236 session.
write (b.data(), b.size());
245 for (
auto const& e : h)
247 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>
263 using boost::asio::buffer_cast;
264 using boost::asio::buffer_size;
267 for(
auto const& b : bs)
268 s.
append(buffer_cast<char const*>(b),
281 session.
close (
true);
289 session.
close (
true);
299 if (postResult ==
nullptr)
304 detachedSession->close(
true);
315 auto const size = boost::asio::buffer_size(buffers);
321 jvResult[jss::type] = jss::error;
322 jvResult[jss::error] =
"jsonInvalid";
324 boost::beast::multi_buffer sb;
326 [&sb](
auto const p,
auto const n)
328 sb.commit(boost::asio::buffer_copy(
329 sb.prepare(n), boost::asio::buffer(p, n)));
332 <<
"Websocket sending '" << jvResult <<
"'";
340 <<
"Websocket received '" << jv <<
"'";
343 [
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"});
366 boost::system::error_code
const&)
386 auto is = std::static_pointer_cast<WSInfoSub> (session->appDefined);
387 if (is->getConsumer().disconnect())
389 session->close({boost::beast::websocket::policy_error,
"threshold exceeded"});
408 jr[jss::type] = jss::response;
409 jr[jss::status] = jss::error;
411 jss::invalid_API_version : jss::missingCommand;
412 jr[jss::request] = jv;
414 jr[jss::id] = jv[jss::id];
416 jr[jss::jsonrpc] = jv[jss::jsonrpc];
418 jr[jss::ripplerpc] = jv[jss::ripplerpc];
420 jr[jss::api_version] = jv[jss::api_version];
454 {is->user(), is->forwarded_for()}};
463 <<
"Exception while processing WS: " << ex.
what() <<
"\n"
467 is->getConsumer().charge(loadType);
468 if (is->getConsumer().warn())
469 jr[jss::warning] = jss::load;
476 if (jr[jss::result].isMember(jss::error))
478 jr = jr[jss::result];
479 jr[jss::status] = jss::error;
485 if (rq.isMember(jss::passphrase.c_str()))
486 rq[jss::passphrase.c_str()] =
"<masked>";
487 if (rq.isMember(jss::secret.c_str()))
488 rq[jss::secret.c_str()] =
"<masked>";
489 if (rq.isMember(jss::seed.c_str()))
490 rq[jss::seed.c_str()] =
"<masked>";
491 if (rq.isMember(jss::seed_hex.c_str()))
492 rq[jss::seed_hex.c_str()] =
"<masked>";
495 jr[jss::request] = rq;
499 jr[jss::status] = jss::success;
503 jr[jss::id] = jv[jss::id];
505 jr[jss::jsonrpc] = jv[jss::jsonrpc];
507 jr[jss::ripplerpc] = jv[jss::ripplerpc];
509 jr[jss::api_version] = jv[jss::api_version];
511 jr[jss::type] = jss::response;
522 session->request().body().data()),
523 session->remoteAddress().at_port (0),
528 session->request().find(
530 if(iter != session->request().end())
531 return iter->value();
532 return boost::beast::string_view{};
538 session->close (
true);
547 sub[
"message"] = std::move(message);
562 boost::string_view
forwardedFor, boost::string_view user)
570 ! reader.
parse (request, jsonOrig) ||
574 HTTPReply (400,
"Unable to parse request: " +
582 if (jsonOrig.
isMember(jss::method) && jsonOrig[jss::method] ==
"batch")
585 if(!jsonOrig.
isMember(jss::params) || !jsonOrig[jss::params].
isArray())
587 HTTPReply (400,
"Malformed batch request", output, rpcJ);
590 size = jsonOrig[jss::params].
size();
595 for (
unsigned i = 0; i < size; ++i)
598 batch ? jsonOrig[jss::params][i] : jsonOrig;
603 r[jss::request] = jsonRPC;
610 if (jsonRPC.
isMember(jss::params) &&
611 jsonRPC[jss::params].
isArray() &&
612 jsonRPC[jss::params].
size() > 0 &&
613 jsonRPC[jss::params][0u].
isObject())
628 HTTPReply (400, jss::invalid_API_version.c_str(), output, rpcJ);
632 r[jss::request] = jsonRPC;
644 if (jsonRPC.
isMember(jss::params) &&
645 jsonRPC[jss::params].
isArray() &&
646 jsonRPC[jss::params].
size() > 0 &&
659 remoteIPAddress, user);
675 HTTPReply(503,
"Server is overloaded", output, rpcJ);
690 HTTPReply (403,
"Forbidden", output, rpcJ);
699 if (!jsonRPC.
isMember(jss::method) || jsonRPC[jss::method].
isNull())
704 HTTPReply (400,
"Null method", output, rpcJ);
719 HTTPReply (400,
"method is not string", output, rpcJ);
729 if (strMethod.
empty())
734 HTTPReply (400,
"method is empty", output, rpcJ);
752 params = jsonRPC [jss::params];
759 HTTPReply (400,
"params unparseable", output, rpcJ);
764 params = std::move (params[0u]);
768 HTTPReply (400,
"params unparseable", output, rpcJ);
779 if (params.
isMember(jss::ripplerpc))
781 if (!params[jss::ripplerpc].isString())
786 HTTPReply(400,
"ripplerpc is not a string", output, rpcJ);
796 ripplerpc = params[jss::ripplerpc].
asString();
812 params[jss::command] = strMethod;
814 <<
"doRpcCommand:" << strMethod <<
":" << params;
834 result[jss::warning] = jss::load;
837 if (ripplerpc >=
"2.0")
841 result[jss::status] = jss::error;
842 result[
"code"] = result[jss::error_code];
843 result[
"message"] = result[jss::error_message];
846 "rpcError: " << result [jss::error] <<
847 ": " << result [jss::error_message];
848 r[jss::error] = std::move(result);
852 result[jss::status] = jss::success;
853 r[jss::result] = std::move(result);
865 if (rq.isMember(jss::passphrase.c_str()))
866 rq[jss::passphrase.c_str()] =
"<masked>";
867 if (rq.isMember(jss::secret.c_str()))
868 rq[jss::secret.c_str()] =
"<masked>";
869 if (rq.isMember(jss::seed.c_str()))
870 rq[jss::seed.c_str()] =
"<masked>";
871 if (rq.isMember(jss::seed_hex.c_str()))
872 rq[jss::seed_hex.c_str()] =
"<masked>";
875 result[jss::status] = jss::error;
876 result[jss::request] = rq;
879 "rpcError: " << result [jss::error] <<
880 ": " << result [jss::error_message];
884 result[jss::status] = jss::success;
886 r[jss::result] = std::move(result);
889 if (params.isMember(jss::jsonrpc))
890 r[jss::jsonrpc] = params[jss::jsonrpc];
891 if (params.isMember(jss::ripplerpc))
892 r[jss::ripplerpc] = params[jss::ripplerpc];
893 if (params.isMember(jss::id))
894 r[jss::id] = params[jss::id];
896 reply.
append(std::move(r));
898 reply = std::move(r);
903 std::chrono::duration_cast <std::chrono::milliseconds> (
912 static const int maxSize = 10000;
913 if (response.size() <= maxSize)
914 stream <<
"Reply: " << response;
916 stream <<
"Reply: " << response.substr (0, maxSize);
932 using namespace boost::beast::http;
934 response<string_body> msg;
938 msg.result(boost::beast::http::status::ok);
939 msg.body() =
"<!DOCTYPE html><html><head><title>" +
systemName() +
940 " Test page for rippled</title></head><body><h1>" +
941 systemName() +
" Test</h1><p>This page shows rippled http(s) "
942 "connectivity is working.</p></body></html>";
946 msg.result(boost::beast::http::status::internal_server_error);
947 msg.body() =
"<HTML><BODY>Server cannot accept clients: " +
948 reason +
"</BODY></HTML>";
950 msg.version(request.version());
952 msg.insert(
"Content-Type",
"text/html");
953 msg.insert(
"Connection",
"close");
954 msg.prepare_payload();
955 handoff.
response = std::make_shared<SimpleWriter>(msg);
968 if (p.ssl_key.empty() && p.ssl_cert.empty() &&
973 p.ssl_key, p.ssl_cert, p.ssl_chain,
979 boost::asio::ssl::context>(
980 boost::asio::ssl::context::sslv23);
994 log <<
"Missing 'ip' in [" << p.
name <<
"]";
995 Throw<std::exception> ();
1001 log <<
"Missing 'port' in [" << p.
name <<
"]";
1002 Throw<std::exception> ();
1004 else if (*parsed.
port == 0)
1006 log <<
"Port " << *parsed.
port <<
"in [" << p.
name <<
"] is invalid";
1007 Throw<std::exception> ();
1017 log <<
"Missing 'protocol' in [" << p.
name <<
"]";
1018 Throw<std::exception> ();
1045 if (! config.
exists(
"server"))
1048 "Required section [server] is missing";
1049 Throw<std::exception> ();
1057 for (
auto const& name : names)
1059 if (! config.
exists(name))
1062 "Missing section: [" << name <<
"]";
1063 Throw<std::exception> ();
1073 auto it = result.
begin ();
1075 while (it != result.
end())
1077 auto& p = it->protocol;
1081 if (p.erase (
"peer") && p.empty())
1082 it = result.
erase (it);
1093 return p.protocol.count(
"peer") != 0;
1098 log <<
"Error: More than one peer protocol configured in [server]";
1099 Throw<std::exception> ();
1103 log <<
"Warning: No peer protocol configured";
1117 if (iter->protocol.count(
"http") > 0 ||
1118 iter->protocol.count(
"https") > 0)
1123 iter->protocol.count(
"https") > 0;
1127 (iter->ip.is_v6() ?
"::1" :
"127.0.0.1") :
1128 iter->ip.to_string();
1143 [](
Port const& port)
1145 return port.protocol.count(
"peer") != 0;
1156 ServerHandler::Setup
1172 boost::asio::io_service& io_service,
JobQueue& jobQueue,
1176 return std::make_unique<ServerHandlerImp>(app, parent,
1177 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.
std::uint16_t ws_queue_limit
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.
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.
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.
void stopped()
Called by derived classes to indicate that the stoppable has stopped.
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.
unsigned int getAPIVersionNumber(Json::Value const &jv)
Retrieve the api version number from the json value.
@ arrayValue
array value (ordered list)
static Json::Value make_json_error(Json::Int code, Json::Value &&message)
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Decorator for streaming out compact json.
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
boost::optional< boost::asio::ip::address > ip
constexpr Json::Int server_overloaded
const Charge feeReferenceRPC
constexpr unsigned int APIVersionIfUnspecified
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_
std::string to_string(ListDisposition disposition)
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.
ServerHandlerImp(Application &app, Stoppable &parent, boost::asio::io_service &io_service, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
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.
Provides an interface for starting and stopping.
static Handoff statusRequestResponse(http_request_type const &request, boost::beast::http::status status)
virtual bool serverOkay(std::string &reason)=0
void onStop() override
Override called when the stop notification is issued.
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
Json::Value processSession(std::shared_ptr< WSSession > const &session, std::shared_ptr< JobQueue::Coro > const &coro, Json::Value const &jv)
static constexpr int maxRequestSize
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_
boost::optional< std::vector< beast::IP::Address > > secure_gateway_ip
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)
constexpr unsigned int APIInvalidVersion
API version numbers used in later API versions.
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.
Role roleRequired(unsigned int version, std::string const &method)
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.
boost::optional< std::uint16_t > port
static std::string buffers_to_string(ConstBufferSequence const &bs)
virtual beast::Journal journal(std::string const &name)=0
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
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()
A version-independent IP address and port combination.
std::shared_ptr< Writer > response
boost::optional< std::vector< beast::IP::Address > > admin_ip
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.
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< ServerHandler > make_ServerHandler(Application &app, Stoppable &parent, boost::asio::io_service &io_service, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
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.