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
21 #include <boost/foreach.hpp> //BOOST_FOREACH
23 /** WWW-Authenticate to present with 401 Unauthorized response */
24 static const char* WWW_AUTH_HEADER_DATA
= "Basic realm=\"jsonrpc\"";
26 /** Simple one-shot callback timer to be used by the RPC mechanism to e.g.
29 class HTTPRPCTimer
: public RPCTimerBase
32 HTTPRPCTimer(struct event_base
* eventBase
, std::function
<void(void)>& func
, int64_t millis
) :
33 ev(eventBase
, false, func
)
36 tv
.tv_sec
= millis
/1000;
37 tv
.tv_usec
= (millis
%1000)*1000;
44 class HTTPRPCTimerInterface
: public RPCTimerInterface
47 HTTPRPCTimerInterface(struct event_base
* _base
) : base(_base
)
54 RPCTimerBase
* NewTimer(std::function
<void(void)>& func
, int64_t millis
)
56 return new HTTPRPCTimer(base
, func
, millis
);
59 struct event_base
* base
;
63 /* Pre-base64-encoded authentication token */
64 static std::string strRPCUserColonPass
;
65 /* Stored RPC timer interface (for unregistration) */
66 static HTTPRPCTimerInterface
* httpRPCTimerInterface
= 0;
68 static void JSONErrorReply(HTTPRequest
* req
, const UniValue
& objError
, const UniValue
& id
)
70 // Send error reply from json-rpc error object
71 int nStatus
= HTTP_INTERNAL_SERVER_ERROR
;
72 int code
= find_value(objError
, "code").get_int();
74 if (code
== RPC_INVALID_REQUEST
)
75 nStatus
= HTTP_BAD_REQUEST
;
76 else if (code
== RPC_METHOD_NOT_FOUND
)
77 nStatus
= HTTP_NOT_FOUND
;
79 std::string strReply
= JSONRPCReply(NullUniValue
, objError
, id
);
81 req
->WriteHeader("Content-Type", "application/json");
82 req
->WriteReply(nStatus
, strReply
);
85 //This function checks username and password against -rpcauth
86 //entries from config file.
87 static bool multiUserAuthorized(std::string strUserPass
)
89 if (strUserPass
.find(":") == std::string::npos
) {
92 std::string strUser
= strUserPass
.substr(0, strUserPass
.find(":"));
93 std::string strPass
= strUserPass
.substr(strUserPass
.find(":") + 1);
95 if (gArgs
.IsArgSet("-rpcauth")) {
96 //Search for multi-user login/pass "rpcauth" from config
97 BOOST_FOREACH(std::string strRPCAuth
, gArgs
.GetArgs("-rpcauth"))
99 std::vector
<std::string
> vFields
;
100 boost::split(vFields
, strRPCAuth
, boost::is_any_of(":$"));
101 if (vFields
.size() != 3) {
102 //Incorrect formatting in config file
106 std::string strName
= vFields
[0];
107 if (!TimingResistantEqual(strName
, strUser
)) {
111 std::string strSalt
= vFields
[1];
112 std::string strHash
= vFields
[2];
114 static const unsigned int KEY_SIZE
= 32;
115 unsigned char out
[KEY_SIZE
];
117 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
);
118 std::vector
<unsigned char> hexvec(out
, out
+KEY_SIZE
);
119 std::string strHashFromPass
= HexStr(hexvec
);
121 if (TimingResistantEqual(strHashFromPass
, strHash
)) {
129 static bool RPCAuthorized(const std::string
& strAuth
, std::string
& strAuthUsernameOut
)
131 if (strRPCUserColonPass
.empty()) // Belt-and-suspenders measure if InitRPCAuthentication was not called
133 if (strAuth
.substr(0, 6) != "Basic ")
135 std::string strUserPass64
= strAuth
.substr(6);
136 boost::trim(strUserPass64
);
137 std::string strUserPass
= DecodeBase64(strUserPass64
);
139 if (strUserPass
.find(":") != std::string::npos
)
140 strAuthUsernameOut
= strUserPass
.substr(0, strUserPass
.find(":"));
142 //Check if authorized under single-user field
143 if (TimingResistantEqual(strUserPass
, strRPCUserColonPass
)) {
146 return multiUserAuthorized(strUserPass
);
149 static bool HTTPReq_JSONRPC(HTTPRequest
* req
, const std::string
&)
151 // JSONRPC handles only POST
152 if (req
->GetRequestMethod() != HTTPRequest::POST
) {
153 req
->WriteReply(HTTP_BAD_METHOD
, "JSONRPC server handles only POST requests");
156 // Check authorization
157 std::pair
<bool, std::string
> authHeader
= req
->GetHeader("authorization");
158 if (!authHeader
.first
) {
159 req
->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA
);
160 req
->WriteReply(HTTP_UNAUTHORIZED
);
165 if (!RPCAuthorized(authHeader
.second
, jreq
.authUser
)) {
166 LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", req
->GetPeer().ToString());
168 /* Deter brute-forcing
169 If this results in a DoS the user really
170 shouldn't have their RPC port exposed. */
173 req
->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA
);
174 req
->WriteReply(HTTP_UNAUTHORIZED
);
181 if (!valRequest
.read(req
->ReadBody()))
182 throw JSONRPCError(RPC_PARSE_ERROR
, "Parse error");
185 jreq
.URI
= req
->GetURI();
187 std::string strReply
;
189 if (valRequest
.isObject()) {
190 jreq
.parse(valRequest
);
192 UniValue result
= tableRPC
.execute(jreq
);
195 strReply
= JSONRPCReply(result
, NullUniValue
, jreq
.id
);
198 } else if (valRequest
.isArray())
199 strReply
= JSONRPCExecBatch(valRequest
.get_array());
201 throw JSONRPCError(RPC_PARSE_ERROR
, "Top-level object parse error");
203 req
->WriteHeader("Content-Type", "application/json");
204 req
->WriteReply(HTTP_OK
, strReply
);
205 } catch (const UniValue
& objError
) {
206 JSONErrorReply(req
, objError
, jreq
.id
);
208 } catch (const std::exception
& e
) {
209 JSONErrorReply(req
, JSONRPCError(RPC_PARSE_ERROR
, e
.what()), jreq
.id
);
215 static bool InitRPCAuthentication()
217 if (GetArg("-rpcpassword", "") == "")
219 LogPrintf("No rpcpassword set - using random cookie authentication\n");
220 if (!GenerateAuthCookie(&strRPCUserColonPass
)) {
221 uiInterface
.ThreadSafeMessageBox(
222 _("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode
223 "", CClientUIInterface::MSG_ERROR
);
227 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");
228 strRPCUserColonPass
= GetArg("-rpcuser", "") + ":" + GetArg("-rpcpassword", "");
235 LogPrint(BCLog::RPC
, "Starting HTTP RPC server\n");
236 if (!InitRPCAuthentication())
239 RegisterHTTPHandler("/", true, HTTPReq_JSONRPC
);
242 httpRPCTimerInterface
= new HTTPRPCTimerInterface(EventBase());
243 RPCSetTimerInterface(httpRPCTimerInterface
);
247 void InterruptHTTPRPC()
249 LogPrint(BCLog::RPC
, "Interrupting HTTP RPC server\n");
254 LogPrint(BCLog::RPC
, "Stopping HTTP RPC server\n");
255 UnregisterHTTPHandler("/", true);
256 if (httpRPCTimerInterface
) {
257 RPCUnsetTimerInterface(httpRPCTimerInterface
);
258 delete httpRPCTimerInterface
;
259 httpRPCTimerInterface
= 0;