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"
19 #include "utilstrencodings.h"
21 #include <boost/algorithm/string.hpp> // boost::trim
22 #include <boost/foreach.hpp> //BOOST_FOREACH
24 /** WWW-Authenticate to present with 401 Unauthorized response */
25 static const char* WWW_AUTH_HEADER_DATA
= "Basic realm=\"jsonrpc\"";
27 /** Simple one-shot callback timer to be used by the RPC mechanism to e.g.
30 class HTTPRPCTimer
: public RPCTimerBase
33 HTTPRPCTimer(struct event_base
* eventBase
, boost::function
<void(void)>& func
, int64_t millis
) :
34 ev(eventBase
, false, func
)
37 tv
.tv_sec
= millis
/1000;
38 tv
.tv_usec
= (millis
%1000)*1000;
45 class HTTPRPCTimerInterface
: public RPCTimerInterface
48 HTTPRPCTimerInterface(struct event_base
* _base
) : base(_base
)
55 RPCTimerBase
* NewTimer(boost::function
<void(void)>& func
, int64_t millis
)
57 return new HTTPRPCTimer(base
, func
, millis
);
60 struct event_base
* base
;
64 /* Pre-base64-encoded authentication token */
65 static std::string strRPCUserColonPass
;
66 /* Stored RPC timer interface (for unregistration) */
67 static HTTPRPCTimerInterface
* httpRPCTimerInterface
= 0;
69 static void JSONErrorReply(HTTPRequest
* req
, const UniValue
& objError
, const UniValue
& id
)
71 // Send error reply from json-rpc error object
72 int nStatus
= HTTP_INTERNAL_SERVER_ERROR
;
73 int code
= find_value(objError
, "code").get_int();
75 if (code
== RPC_INVALID_REQUEST
)
76 nStatus
= HTTP_BAD_REQUEST
;
77 else if (code
== RPC_METHOD_NOT_FOUND
)
78 nStatus
= HTTP_NOT_FOUND
;
80 std::string strReply
= JSONRPCReply(NullUniValue
, objError
, id
);
82 req
->WriteHeader("Content-Type", "application/json");
83 req
->WriteReply(nStatus
, strReply
);
86 //This function checks username and password against -rpcauth
87 //entries from config file.
88 static bool multiUserAuthorized(std::string strUserPass
)
90 if (strUserPass
.find(":") == std::string::npos
) {
93 std::string strUser
= strUserPass
.substr(0, strUserPass
.find(":"));
94 std::string strPass
= strUserPass
.substr(strUserPass
.find(":") + 1);
96 if (mapMultiArgs
.count("-rpcauth") > 0) {
97 //Search for multi-user login/pass "rpcauth" from config
98 BOOST_FOREACH(std::string strRPCAuth
, mapMultiArgs
.at("-rpcauth"))
100 std::vector
<std::string
> vFields
;
101 boost::split(vFields
, strRPCAuth
, boost::is_any_of(":$"));
102 if (vFields
.size() != 3) {
103 //Incorrect formatting in config file
107 std::string strName
= vFields
[0];
108 if (!TimingResistantEqual(strName
, strUser
)) {
112 std::string strSalt
= vFields
[1];
113 std::string strHash
= vFields
[2];
115 static const unsigned int KEY_SIZE
= 32;
116 unsigned char out
[KEY_SIZE
];
118 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
);
119 std::vector
<unsigned char> hexvec(out
, out
+KEY_SIZE
);
120 std::string strHashFromPass
= HexStr(hexvec
);
122 if (TimingResistantEqual(strHashFromPass
, strHash
)) {
130 static bool RPCAuthorized(const std::string
& strAuth
, std::string
& strAuthUsernameOut
)
132 if (strRPCUserColonPass
.empty()) // Belt-and-suspenders measure if InitRPCAuthentication was not called
134 if (strAuth
.substr(0, 6) != "Basic ")
136 std::string strUserPass64
= strAuth
.substr(6);
137 boost::trim(strUserPass64
);
138 std::string strUserPass
= DecodeBase64(strUserPass64
);
140 if (strUserPass
.find(":") != std::string::npos
)
141 strAuthUsernameOut
= strUserPass
.substr(0, strUserPass
.find(":"));
143 //Check if authorized under single-user field
144 if (TimingResistantEqual(strUserPass
, strRPCUserColonPass
)) {
147 return multiUserAuthorized(strUserPass
);
150 static bool HTTPReq_JSONRPC(HTTPRequest
* req
, const std::string
&)
152 // JSONRPC handles only POST
153 if (req
->GetRequestMethod() != HTTPRequest::POST
) {
154 req
->WriteReply(HTTP_BAD_METHOD
, "JSONRPC server handles only POST requests");
157 // Check authorization
158 std::pair
<bool, std::string
> authHeader
= req
->GetHeader("authorization");
159 if (!authHeader
.first
) {
160 req
->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA
);
161 req
->WriteReply(HTTP_UNAUTHORIZED
);
166 if (!RPCAuthorized(authHeader
.second
, jreq
.authUser
)) {
167 LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", req
->GetPeer().ToString());
169 /* Deter brute-forcing
170 If this results in a DoS the user really
171 shouldn't have their RPC port exposed. */
174 req
->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA
);
175 req
->WriteReply(HTTP_UNAUTHORIZED
);
182 if (!valRequest
.read(req
->ReadBody()))
183 throw JSONRPCError(RPC_PARSE_ERROR
, "Parse error");
186 jreq
.URI
= req
->GetURI();
188 std::string strReply
;
190 if (valRequest
.isObject()) {
191 jreq
.parse(valRequest
);
193 UniValue result
= tableRPC
.execute(jreq
);
196 strReply
= JSONRPCReply(result
, NullUniValue
, jreq
.id
);
199 } else if (valRequest
.isArray())
200 strReply
= JSONRPCExecBatch(valRequest
.get_array());
202 throw JSONRPCError(RPC_PARSE_ERROR
, "Top-level object parse error");
204 req
->WriteHeader("Content-Type", "application/json");
205 req
->WriteReply(HTTP_OK
, strReply
);
206 } catch (const UniValue
& objError
) {
207 JSONErrorReply(req
, objError
, jreq
.id
);
209 } catch (const std::exception
& e
) {
210 JSONErrorReply(req
, JSONRPCError(RPC_PARSE_ERROR
, e
.what()), jreq
.id
);
216 static bool InitRPCAuthentication()
218 if (GetArg("-rpcpassword", "") == "")
220 LogPrintf("No rpcpassword set - using random cookie authentication\n");
221 if (!GenerateAuthCookie(&strRPCUserColonPass
)) {
222 uiInterface
.ThreadSafeMessageBox(
223 _("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode
224 "", CClientUIInterface::MSG_ERROR
);
228 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");
229 strRPCUserColonPass
= GetArg("-rpcuser", "") + ":" + GetArg("-rpcpassword", "");
236 LogPrint("rpc", "Starting HTTP RPC server\n");
237 if (!InitRPCAuthentication())
240 RegisterHTTPHandler("/", true, HTTPReq_JSONRPC
);
243 httpRPCTimerInterface
= new HTTPRPCTimerInterface(EventBase());
244 RPCSetTimerInterface(httpRPCTimerInterface
);
248 void InterruptHTTPRPC()
250 LogPrint("rpc", "Interrupting HTTP RPC server\n");
255 LogPrint("rpc", "Stopping HTTP RPC server\n");
256 UnregisterHTTPHandler("/", true);
257 if (httpRPCTimerInterface
) {
258 RPCUnsetTimerInterface(httpRPCTimerInterface
);
259 delete httpRPCTimerInterface
;
260 httpRPCTimerInterface
= 0;