1 // Copyright (c) 2015-2016 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
8 #include <chainparams.h>
9 #include <httpserver.h>
10 #include <rpc/protocol.h>
11 #include <rpc/server.h>
15 #include <utilstrencodings.h>
16 #include <ui_interface.h>
17 #include <crypto/hmac_sha256.h>
20 #include <boost/algorithm/string.hpp> // boost::trim
22 /** WWW-Authenticate to present with 401 Unauthorized response */
23 static const char* WWW_AUTH_HEADER_DATA
= "Basic realm=\"jsonrpc\"";
25 /** Simple one-shot callback timer to be used by the RPC mechanism to e.g.
28 class HTTPRPCTimer
: public RPCTimerBase
31 HTTPRPCTimer(struct event_base
* eventBase
, std::function
<void(void)>& func
, int64_t millis
) :
32 ev(eventBase
, false, func
)
35 tv
.tv_sec
= millis
/1000;
36 tv
.tv_usec
= (millis
%1000)*1000;
43 class HTTPRPCTimerInterface
: public RPCTimerInterface
46 explicit HTTPRPCTimerInterface(struct event_base
* _base
) : base(_base
)
49 const char* Name() override
53 RPCTimerBase
* NewTimer(std::function
<void(void)>& func
, int64_t millis
) override
55 return new HTTPRPCTimer(base
, func
, millis
);
58 struct event_base
* base
;
62 /* Pre-base64-encoded authentication token */
63 static std::string strRPCUserColonPass
;
64 /* Stored RPC timer interface (for unregistration) */
65 static std::unique_ptr
<HTTPRPCTimerInterface
> httpRPCTimerInterface
;
67 static void JSONErrorReply(HTTPRequest
* req
, const UniValue
& objError
, const UniValue
& id
)
69 // Send error reply from json-rpc error object
70 int nStatus
= HTTP_INTERNAL_SERVER_ERROR
;
71 int code
= find_value(objError
, "code").get_int();
73 if (code
== RPC_INVALID_REQUEST
)
74 nStatus
= HTTP_BAD_REQUEST
;
75 else if (code
== RPC_METHOD_NOT_FOUND
)
76 nStatus
= HTTP_NOT_FOUND
;
78 std::string strReply
= JSONRPCReply(NullUniValue
, objError
, id
);
80 req
->WriteHeader("Content-Type", "application/json");
81 req
->WriteReply(nStatus
, strReply
);
84 //This function checks username and password against -rpcauth
85 //entries from config file.
86 static bool multiUserAuthorized(std::string strUserPass
)
88 if (strUserPass
.find(":") == std::string::npos
) {
91 std::string strUser
= strUserPass
.substr(0, strUserPass
.find(":"));
92 std::string strPass
= strUserPass
.substr(strUserPass
.find(":") + 1);
94 for (const std::string
& strRPCAuth
: gArgs
.GetArgs("-rpcauth")) {
95 //Search for multi-user login/pass "rpcauth" from config
96 std::vector
<std::string
> vFields
;
97 boost::split(vFields
, strRPCAuth
, boost::is_any_of(":$"));
98 if (vFields
.size() != 3) {
99 //Incorrect formatting in config file
103 std::string strName
= vFields
[0];
104 if (!TimingResistantEqual(strName
, strUser
)) {
108 std::string strSalt
= vFields
[1];
109 std::string strHash
= vFields
[2];
111 static const unsigned int KEY_SIZE
= 32;
112 unsigned char out
[KEY_SIZE
];
114 CHMAC_SHA256(reinterpret_cast<const unsigned char*>(strSalt
.c_str()), strSalt
.size()).Write(reinterpret_cast<const unsigned char*>(strPass
.c_str()), strPass
.size()).Finalize(out
);
115 std::vector
<unsigned char> hexvec(out
, out
+KEY_SIZE
);
116 std::string strHashFromPass
= HexStr(hexvec
);
118 if (TimingResistantEqual(strHashFromPass
, strHash
)) {
125 static bool RPCAuthorized(const std::string
& strAuth
, std::string
& strAuthUsernameOut
)
127 if (strRPCUserColonPass
.empty()) // Belt-and-suspenders measure if InitRPCAuthentication was not called
129 if (strAuth
.substr(0, 6) != "Basic ")
131 std::string strUserPass64
= strAuth
.substr(6);
132 boost::trim(strUserPass64
);
133 std::string strUserPass
= DecodeBase64(strUserPass64
);
135 if (strUserPass
.find(":") != std::string::npos
)
136 strAuthUsernameOut
= strUserPass
.substr(0, strUserPass
.find(":"));
138 //Check if authorized under single-user field
139 if (TimingResistantEqual(strUserPass
, strRPCUserColonPass
)) {
142 return multiUserAuthorized(strUserPass
);
145 static bool HTTPReq_JSONRPC(HTTPRequest
* req
, const std::string
&)
147 // JSONRPC handles only POST
148 if (req
->GetRequestMethod() != HTTPRequest::POST
) {
149 req
->WriteReply(HTTP_BAD_METHOD
, "JSONRPC server handles only POST requests");
152 // Check authorization
153 std::pair
<bool, std::string
> authHeader
= req
->GetHeader("authorization");
154 if (!authHeader
.first
) {
155 req
->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA
);
156 req
->WriteReply(HTTP_UNAUTHORIZED
);
161 if (!RPCAuthorized(authHeader
.second
, jreq
.authUser
)) {
162 LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", req
->GetPeer().ToString());
164 /* Deter brute-forcing
165 If this results in a DoS the user really
166 shouldn't have their RPC port exposed. */
169 req
->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA
);
170 req
->WriteReply(HTTP_UNAUTHORIZED
);
177 if (!valRequest
.read(req
->ReadBody()))
178 throw JSONRPCError(RPC_PARSE_ERROR
, "Parse error");
181 jreq
.URI
= req
->GetURI();
183 std::string strReply
;
185 if (valRequest
.isObject()) {
186 jreq
.parse(valRequest
);
188 UniValue result
= tableRPC
.execute(jreq
);
191 strReply
= JSONRPCReply(result
, NullUniValue
, jreq
.id
);
194 } else if (valRequest
.isArray())
195 strReply
= JSONRPCExecBatch(jreq
, valRequest
.get_array());
197 throw JSONRPCError(RPC_PARSE_ERROR
, "Top-level object parse error");
199 req
->WriteHeader("Content-Type", "application/json");
200 req
->WriteReply(HTTP_OK
, strReply
);
201 } catch (const UniValue
& objError
) {
202 JSONErrorReply(req
, objError
, jreq
.id
);
204 } catch (const std::exception
& e
) {
205 JSONErrorReply(req
, JSONRPCError(RPC_PARSE_ERROR
, e
.what()), jreq
.id
);
211 static bool InitRPCAuthentication()
213 if (gArgs
.GetArg("-rpcpassword", "") == "")
215 LogPrintf("No rpcpassword set - using random cookie authentication\n");
216 if (!GenerateAuthCookie(&strRPCUserColonPass
)) {
217 uiInterface
.ThreadSafeMessageBox(
218 _("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode
219 "", CClientUIInterface::MSG_ERROR
);
223 LogPrintf("Config options rpcuser and rpcpassword will soon be deprecated. Locally-run instances may remove rpcuser to use cookie-based auth, or may be replaced with rpcauth. Please see share/rpcuser for rpcauth auth generation.\n");
224 strRPCUserColonPass
= gArgs
.GetArg("-rpcuser", "") + ":" + gArgs
.GetArg("-rpcpassword", "");
231 LogPrint(BCLog::RPC
, "Starting HTTP RPC server\n");
232 if (!InitRPCAuthentication())
235 RegisterHTTPHandler("/", true, HTTPReq_JSONRPC
);
237 // ifdef can be removed once we switch to better endpoint support and API versioning
238 RegisterHTTPHandler("/wallet/", false, HTTPReq_JSONRPC
);
241 httpRPCTimerInterface
= MakeUnique
<HTTPRPCTimerInterface
>(EventBase());
242 RPCSetTimerInterface(httpRPCTimerInterface
.get());
246 void InterruptHTTPRPC()
248 LogPrint(BCLog::RPC
, "Interrupting HTTP RPC server\n");
253 LogPrint(BCLog::RPC
, "Stopping HTTP RPC server\n");
254 UnregisterHTTPHandler("/", true);
255 if (httpRPCTimerInterface
) {
256 RPCUnsetTimerInterface(httpRPCTimerInterface
.get());
257 httpRPCTimerInterface
.reset();