diff --git a/rippled-example.cfg b/rippled-example.cfg index b2c683117f..4dbd586681 100644 --- a/rippled-example.cfg +++ b/rippled-example.cfg @@ -83,12 +83,12 @@ # # [peer_ip]: # IP address or domain to bind to allow external connections from peers. -# Defaults to not allow external connections from peers. +# Defaults to not binding, which disallows external connections from peers. # # Examples: 0.0.0.0 - Bind on all interfaces. # # [peer_port]: -# Port to bind to allow external connections from peers. +# If peer_ip is supplied, corresponding port to bind to for peer connections. # # [peer_private]: # 0 or 1. @@ -97,14 +97,27 @@ # # [rpc_ip]: # IP address or domain to bind to allow insecure RPC connections. -# Defaults to not allow RPC connections. +# Defaults to not binding, which disallows RPC connections. # # [rpc_port]: -# Port to bind to if allowing insecure RPC connections. +# If rpc_ip is supplied, corresponding port to bind to for peer connections. # # [rpc_allow_remote]: # 0 or 1. -# 0: only allows RPC connections from 127.0.0.1. [default] +# 0: Allow RPC connections only from 127.0.0.1. [default] +# 1: Allow RPC connections from any IP. +# +# [rpc_admin_user]: +# As a server, require a this user to specified and require admin_password to +# be checked for RPC admin functions. +# +# As a client, supply this to the server. +# +# [rpc_admin_password]: +# As a server, require a this password to specified and require admin_user to +# be checked for RPC admin functions. +# +# As a client, supply this to the server. # # [websocket_public_ip]: # IP address or domain to bind to allow untrusted connections from clients. diff --git a/src/cpp/ripple/CallRPC.cpp b/src/cpp/ripple/CallRPC.cpp index 27b173a2c1..a48e069595 100644 --- a/src/cpp/ripple/CallRPC.cpp +++ b/src/cpp/ripple/CallRPC.cpp @@ -565,10 +565,6 @@ int commandLineRPC(const std::vector& vCmd) RPCParser rpParser; Json::Value jvRpcParams(Json::arrayValue); - if (theConfig.RPC_USER.empty() && theConfig.RPC_PASSWORD.empty()) - throw std::runtime_error("You must set rpcpassword= in the configuration file. " - "If the file does not exist, create it with owner-readable-only file permissions."); - if (vCmd.empty()) return 1; // 1 = print usage. for (int i = 1; i != vCmd.size(); i++) @@ -597,8 +593,8 @@ int commandLineRPC(const std::vector& vCmd) jvOutput = callRPC( theConfig.RPC_IP, theConfig.RPC_PORT, - theConfig.RPC_USER, - theConfig.RPC_PASSWORD, + theConfig.RPC_ADMIN_USER, + theConfig.RPC_ADMIN_PASSWORD, "", jvRequest.isMember("method") // Allow parser to rewrite method. ? jvRequest["method"].asString() diff --git a/src/cpp/ripple/Config.cpp b/src/cpp/ripple/Config.cpp index a886e27fab..a44acdc0f9 100644 --- a/src/cpp/ripple/Config.cpp +++ b/src/cpp/ripple/Config.cpp @@ -35,6 +35,8 @@ #define SECTION_PEER_SSL_CIPHER_LIST "peer_ssl_cipher_list" #define SECTION_PEER_START_MAX "peer_start_max" #define SECTION_RPC_ALLOW_REMOTE "rpc_allow_remote" +#define SECTION_RPC_ADMIN_USER "rpc_admin_user" +#define SECTION_RPC_ADMIN_PASSWORD "rpc_admin_password" #define SECTION_RPC_IP "rpc_ip" #define SECTION_RPC_PORT "rpc_port" #define SECTION_RPC_STARTUP "rpc_startup" @@ -179,8 +181,6 @@ Config::Config() LEDGER_SECONDS = 60; LEDGER_CREATOR = false; - RPC_USER = "admin"; - RPC_PASSWORD = "pass"; RPC_ALLOW_REMOTE = false; PEER_SSL_CIPHER_LIST = DEFAULT_PEER_SSL_CIPHER_LIST; @@ -298,6 +298,8 @@ void Config::load() if (sectionSingleB(secConfig, SECTION_PEER_PRIVATE, strTemp)) PEER_PRIVATE = boost::lexical_cast(strTemp); + (void) sectionSingleB(secConfig, SECTION_RPC_ADMIN_USER, RPC_ADMIN_USER); + (void) sectionSingleB(secConfig, SECTION_RPC_ADMIN_PASSWORD, RPC_ADMIN_PASSWORD); (void) sectionSingleB(secConfig, SECTION_RPC_IP, RPC_IP); if (sectionSingleB(secConfig, SECTION_RPC_PORT, strTemp)) diff --git a/src/cpp/ripple/Config.h b/src/cpp/ripple/Config.h index 1144140372..a72be021ba 100644 --- a/src/cpp/ripple/Config.h +++ b/src/cpp/ripple/Config.h @@ -112,8 +112,8 @@ public: // RPC parameters std::string RPC_IP; int RPC_PORT; - std::string RPC_USER; - std::string RPC_PASSWORD; + std::string RPC_ADMIN_USER; + std::string RPC_ADMIN_PASSWORD; bool RPC_ALLOW_REMOTE; std::vector RPC_STARTUP; diff --git a/src/cpp/ripple/RPCDoor.cpp b/src/cpp/ripple/RPCDoor.cpp index b52e675f53..219f281970 100644 --- a/src/cpp/ripple/RPCDoor.cpp +++ b/src/cpp/ripple/RPCDoor.cpp @@ -37,6 +37,7 @@ bool RPCDoor::isClientAllowed(const std::string& ip) { if (theConfig.RPC_ALLOW_REMOTE) return true; + if (ip == "127.0.0.1") return true; diff --git a/src/cpp/ripple/RPCErr.cpp b/src/cpp/ripple/RPCErr.cpp index d09283fbd4..4c4958a1dd 100644 --- a/src/cpp/ripple/RPCErr.cpp +++ b/src/cpp/ripple/RPCErr.cpp @@ -25,6 +25,7 @@ Json::Value rpcError(int iError, Json::Value jvResult) { rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed." }, { rpcDST_ACT_MISSING, "dstActMissing", "Destination account does not exists." }, { rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency/issuer is malformed." }, + { rpcFORBIDDEN, "forbidden", "Bad credentials." }, { rpcFAIL_GEN_DECRPYT, "failGenDecrypt", "Failed to decrypt generator." }, { rpcGETS_ACT_MALFORMED, "getsActMalformed", "Gets account malformed." }, { rpcGETS_AMT_MALFORMED, "getsAmtMalformed", "Gets amount malformed." }, diff --git a/src/cpp/ripple/RPCErr.h b/src/cpp/ripple/RPCErr.h index 0057c6af2f..611cc5eea1 100644 --- a/src/cpp/ripple/RPCErr.h +++ b/src/cpp/ripple/RPCErr.h @@ -7,6 +7,7 @@ enum { rpcSUCCESS = 0, rpcBAD_SYNTAX, // Must be 1 to print usage to command line. rpcJSON_RPC, + rpcFORBIDDEN, // Error numbers beyond this line are not stable between versions. // Programs should use error tokens. diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index c3d93f66ca..c7141e49f9 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -25,6 +25,38 @@ SETUP_LOG(); +int iAdminGet(const Json::Value& jvRequest, const std::string& strRemoteIp) +{ + int iRole; + bool bPasswordSupplied = jvRequest.isMember("user") || jvRequest.isMember("password"); + bool bPasswordRequired = !theConfig.RPC_ADMIN_USER.empty() || !theConfig.RPC_ADMIN_PASSWORD.empty(); + + bool bPasswordWrong = bPasswordSupplied + ? bPasswordRequired + // Supplied, required, and incorrect. + ? theConfig.RPC_ADMIN_USER != (jvRequest.isMember("user") ? jvRequest["user"].asString() : "") + || theConfig.RPC_ADMIN_PASSWORD != (jvRequest.isMember("user") ? jvRequest["password"].asString() : "") + // Supplied and not required. + : true + : false; + // Meets IP restriction for admin. + bool bAdminIP = strRemoteIp == "127.0.0.1"; + + if (bPasswordWrong // Wrong + || (bPasswordSupplied && !bAdminIP)) // Supplied and doesn't meet IP filter. + { + iRole = RPCHandler::FORBID; + } + // If supplied, password is correct. + else + { + // Allow admin, if from admin IP and no password is required or it was supplied and correct. + iRole = bAdminIP && (!bPasswordRequired || bPasswordSupplied) ? RPCHandler::ADMIN : RPCHandler::GUEST; + } + + return iRole; +} + RPCHandler::RPCHandler(NetworkOPs* netOps) { mNetOps = netOps; @@ -2314,11 +2346,8 @@ Json::Value RPCHandler::doSubscribe(Json::Value jvRequest) if (jvRequest.isMember("url")) { -// Temporarily off. -#if 0 if (mRole != ADMIN) return rpcError(rpcNO_PERMISSION); -#endif std::string strUrl = jvRequest["url"].asString(); std::string strUsername = jvRequest.isMember("username") ? jvRequest["username"].asString() : ""; diff --git a/src/cpp/ripple/RPCHandler.h b/src/cpp/ripple/RPCHandler.h index 3c79a73631..455d6dfd47 100644 --- a/src/cpp/ripple/RPCHandler.h +++ b/src/cpp/ripple/RPCHandler.h @@ -110,7 +110,7 @@ class RPCHandler public: - enum { GUEST, USER, ADMIN }; + enum { GUEST, USER, ADMIN, FORBID }; RPCHandler(NetworkOPs* netOps); RPCHandler(NetworkOPs* netOps, InfoSub* infoSub); @@ -136,5 +136,7 @@ public: static Json::Value runHandler(const std::string& name, const Json::Value& params); }; +int iAdminGet(const Json::Value& jvRequest, const std::string& strRemoteIp); + #endif // vim:ts=4 diff --git a/src/cpp/ripple/RPCServer.cpp b/src/cpp/ripple/RPCServer.cpp index fa942fc9ed..d56ddd8034 100644 --- a/src/cpp/ripple/RPCServer.cpp +++ b/src/cpp/ripple/RPCServer.cpp @@ -32,9 +32,6 @@ RPCServer::RPCServer(boost::asio::io_service& io_service , NetworkOPs* nopNetwor void RPCServer::connected() { //std::cerr << "RPC request" << std::endl; - if (mSocket.remote_endpoint().address().to_string()=="127.0.0.1") mRole = RPCHandler::ADMIN; - else mRole = RPCHandler::GUEST; - boost::asio::async_read_until(mSocket, mLineBuffer, "\r\n", boost::bind(&RPCServer::handle_read_line, shared_from_this(), boost::asio::placeholders::error)); } @@ -114,16 +111,17 @@ std::string RPCServer::handleRequest(const std::string& requestStr) Json::Value id; // Parse request - Json::Value valRequest; - Json::Reader reader; - if (!reader.parse(requestStr, valRequest) || valRequest.isNull() || !valRequest.isObject()) + Json::Value jvRequest; + Json::Reader reader; + + if (!reader.parse(requestStr, jvRequest) || jvRequest.isNull() || !jvRequest.isObject()) return(HTTPReply(400, "unable to parse request")); // Parse id now so errors from here on will have the id - id = valRequest["id"]; + id = jvRequest["id"]; // Parse method - Json::Value valMethod = valRequest["method"]; + Json::Value valMethod = jvRequest["method"]; if (valMethod.isNull()) return(HTTPReply(400, "null method")); if (!valMethod.isString()) @@ -131,11 +129,24 @@ std::string RPCServer::handleRequest(const std::string& requestStr) std::string strMethod = valMethod.asString(); // Parse params - Json::Value valParams = valRequest["params"]; + Json::Value valParams = jvRequest["params"]; + if (valParams.isNull()) + { valParams = Json::Value(Json::arrayValue); + } else if (!valParams.isArray()) - return(HTTPReply(400, "params unparseable")); + { + return HTTPReply(400, "params unparseable"); + } + + mRole = iAdminGet(jvRequest, mSocket.remote_endpoint().address().to_string()); + + if (RPCHandler::FORBID == mRole) + { + // XXX This needs rate limiting to prevent brute forcing password. + return HTTPReply(403, "Forbidden"); + } RPCHandler mRPCHandler(mNetOps); diff --git a/src/cpp/ripple/WSConnection.h b/src/cpp/ripple/WSConnection.h index 6bc97e6cae..90d43d82eb 100644 --- a/src/cpp/ripple/WSConnection.h +++ b/src/cpp/ripple/WSConnection.h @@ -12,6 +12,7 @@ #include "CallRPC.h" #include "InstanceCounter.h" #include "Log.h" +#include "RPCErr.h" DEFINE_INSTANCE(WebSocketConnection); @@ -91,9 +92,18 @@ public: RPCHandler mRPCHandler(&mNetwork, this); Json::Value jvResult(Json::objectValue); - jvResult["result"] = mRPCHandler.doCommand( - jvRequest, - mHandler->getPublic() ? RPCHandler::GUEST : RPCHandler::ADMIN); + int iRole = mHandler->getPublic() + ? RPCHandler::GUEST // Don't check on the public interface. + : iAdminGet(jvRequest, "127.0.0.1"); // XXX Fix this to return the remote IP. + + if (RPCHandler::FORBID == iRole) + { + jvResult["result"] = rpcError(rpcFORBIDDEN); + } + else + { + jvResult["result"] = mRPCHandler.doCommand(jvRequest, iRole); + } // Currently we will simply unwrap errors returned by the RPC // API, in the future maybe we can make the responses