Merge #11722: Switched sync.{cpp,h} to std threading primitives.
[bitcoinplatinum.git] / src / httprpc.cpp
blob6b6849e59bd44c2ce09e2cd83464ca653ea32abc
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.
5 #include <httprpc.h>
7 #include <base58.h>
8 #include <chainparams.h>
9 #include <httpserver.h>
10 #include <rpc/protocol.h>
11 #include <rpc/server.h>
12 #include <random.h>
13 #include <sync.h>
14 #include <util.h>
15 #include <utilstrencodings.h>
16 #include <ui_interface.h>
17 #include <crypto/hmac_sha256.h>
18 #include <stdio.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.
26 * re-lock the wallet.
28 class HTTPRPCTimer : public RPCTimerBase
30 public:
31 HTTPRPCTimer(struct event_base* eventBase, std::function<void(void)>& func, int64_t millis) :
32 ev(eventBase, false, func)
34 struct timeval tv;
35 tv.tv_sec = millis/1000;
36 tv.tv_usec = (millis%1000)*1000;
37 ev.trigger(&tv);
39 private:
40 HTTPEvent ev;
43 class HTTPRPCTimerInterface : public RPCTimerInterface
45 public:
46 explicit HTTPRPCTimerInterface(struct event_base* _base) : base(_base)
49 const char* Name() override
51 return "HTTP";
53 RPCTimerBase* NewTimer(std::function<void(void)>& func, int64_t millis) override
55 return new HTTPRPCTimer(base, func, millis);
57 private:
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) {
89 return false;
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
100 continue;
103 std::string strName = vFields[0];
104 if (!TimingResistantEqual(strName, strUser)) {
105 continue;
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)) {
119 return true;
122 return false;
125 static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUsernameOut)
127 if (strRPCUserColonPass.empty()) // Belt-and-suspenders measure if InitRPCAuthentication was not called
128 return false;
129 if (strAuth.substr(0, 6) != "Basic ")
130 return false;
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)) {
140 return true;
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");
150 return false;
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);
157 return false;
160 JSONRPCRequest jreq;
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. */
167 MilliSleep(250);
169 req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
170 req->WriteReply(HTTP_UNAUTHORIZED);
171 return false;
174 try {
175 // Parse request
176 UniValue valRequest;
177 if (!valRequest.read(req->ReadBody()))
178 throw JSONRPCError(RPC_PARSE_ERROR, "Parse error");
180 // Set the URI
181 jreq.URI = req->GetURI();
183 std::string strReply;
184 // singleton request
185 if (valRequest.isObject()) {
186 jreq.parse(valRequest);
188 UniValue result = tableRPC.execute(jreq);
190 // Send reply
191 strReply = JSONRPCReply(result, NullUniValue, jreq.id);
193 // array of requests
194 } else if (valRequest.isArray())
195 strReply = JSONRPCExecBatch(jreq, valRequest.get_array());
196 else
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);
203 return false;
204 } catch (const std::exception& e) {
205 JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
206 return false;
208 return true;
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);
220 return false;
222 } else {
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", "");
226 return true;
229 bool StartHTTPRPC()
231 LogPrint(BCLog::RPC, "Starting HTTP RPC server\n");
232 if (!InitRPCAuthentication())
233 return false;
235 RegisterHTTPHandler("/", true, HTTPReq_JSONRPC);
236 #ifdef ENABLE_WALLET
237 // ifdef can be removed once we switch to better endpoint support and API versioning
238 RegisterHTTPHandler("/wallet/", false, HTTPReq_JSONRPC);
239 #endif
240 assert(EventBase());
241 httpRPCTimerInterface = MakeUnique<HTTPRPCTimerInterface>(EventBase());
242 RPCSetTimerInterface(httpRPCTimerInterface.get());
243 return true;
246 void InterruptHTTPRPC()
248 LogPrint(BCLog::RPC, "Interrupting HTTP RPC server\n");
251 void StopHTTPRPC()
253 LogPrint(BCLog::RPC, "Stopping HTTP RPC server\n");
254 UnregisterHTTPHandler("/", true);
255 if (httpRPCTimerInterface) {
256 RPCUnsetTimerInterface(httpRPCTimerInterface.get());
257 httpRPCTimerInterface.reset();