1 // Copyright (c) 2015-2018 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.
4 
5 #include <httprpc.h>
6 
7 #include <chainparams.h>
8 #include <httpserver.h>
9 #include <key_io.h>
10 #include <rpc/protocol.h>
11 #include <rpc/server.h>
12 #include <random.h>
13 #include <sync.h>
14 #include <util/system.h>
15 #include <util/strencodings.h>
16 #include <ui_interface.h>
17 #include <walletinitinterface.h>
18 #include <crypto/hmac_sha256.h>
19 #include <stdio.h>
20 
21 #include <memory>
22 
23 #include <boost/algorithm/string.hpp> // boost::trim
24 
25 /** WWW-Authenticate to present with 401 Unauthorized response */
26 static const char* WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\"";
27 
28 /** Simple one-shot callback timer to be used by the RPC mechanism to e.g.
29  * re-lock the wallet.
30  */
31 class HTTPRPCTimer : public RPCTimerBase
32 {
33 public:
HTTPRPCTimer(struct event_base * eventBase,std::function<void ()> & func,int64_t millis)34     HTTPRPCTimer(struct event_base* eventBase, std::function<void()>& func, int64_t millis) :
35         ev(eventBase, false, func)
36     {
37         struct timeval tv;
38         tv.tv_sec = millis/1000;
39         tv.tv_usec = (millis%1000)*1000;
40         ev.trigger(&tv);
41     }
42 private:
43     HTTPEvent ev;
44 };
45 
46 class HTTPRPCTimerInterface : public RPCTimerInterface
47 {
48 public:
HTTPRPCTimerInterface(struct event_base * _base)49     explicit HTTPRPCTimerInterface(struct event_base* _base) : base(_base)
50     {
51     }
Name()52     const char* Name() override
53     {
54         return "HTTP";
55     }
NewTimer(std::function<void ()> & func,int64_t millis)56     RPCTimerBase* NewTimer(std::function<void()>& func, int64_t millis) override
57     {
58         return new HTTPRPCTimer(base, func, millis);
59     }
60 private:
61     struct event_base* base;
62 };
63 
64 
65 /* Pre-base64-encoded authentication token */
66 static std::string strRPCUserColonPass;
67 /* Stored RPC timer interface (for unregistration) */
68 static std::unique_ptr<HTTPRPCTimerInterface> httpRPCTimerInterface;
69 
JSONErrorReply(HTTPRequest * req,const UniValue & objError,const UniValue & id)70 static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id)
71 {
72     // Send error reply from json-rpc error object
73     int nStatus = HTTP_INTERNAL_SERVER_ERROR;
74     int code = find_value(objError, "code").get_int();
75 
76     if (code == RPC_INVALID_REQUEST)
77         nStatus = HTTP_BAD_REQUEST;
78     else if (code == RPC_METHOD_NOT_FOUND)
79         nStatus = HTTP_NOT_FOUND;
80 
81     std::string strReply = JSONRPCReply(NullUniValue, objError, id);
82 
83     req->WriteHeader("Content-Type", "application/json");
84     req->WriteReply(nStatus, strReply);
85 }
86 
87 //This function checks username and password against -rpcauth
88 //entries from config file.
multiUserAuthorized(std::string strUserPass)89 static bool multiUserAuthorized(std::string strUserPass)
90 {
91     if (strUserPass.find(':') == std::string::npos) {
92         return false;
93     }
94     std::string strUser = strUserPass.substr(0, strUserPass.find(':'));
95     std::string strPass = strUserPass.substr(strUserPass.find(':') + 1);
96 
97     for (const std::string& strRPCAuth : gArgs.GetArgs("-rpcauth")) {
98         //Search for multi-user login/pass "rpcauth" from config
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
103             continue;
104         }
105 
106         std::string strName = vFields[0];
107         if (!TimingResistantEqual(strName, strUser)) {
108             continue;
109         }
110 
111         std::string strSalt = vFields[1];
112         std::string strHash = vFields[2];
113 
114         static const unsigned int KEY_SIZE = 32;
115         unsigned char out[KEY_SIZE];
116 
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);
120 
121         if (TimingResistantEqual(strHashFromPass, strHash)) {
122             return true;
123         }
124     }
125     return false;
126 }
127 
RPCAuthorized(const std::string & strAuth,std::string & strAuthUsernameOut)128 static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUsernameOut)
129 {
130     if (strRPCUserColonPass.empty()) // Belt-and-suspenders measure if InitRPCAuthentication was not called
131         return false;
132     if (strAuth.substr(0, 6) != "Basic ")
133         return false;
134     std::string strUserPass64 = strAuth.substr(6);
135     boost::trim(strUserPass64);
136     std::string strUserPass = DecodeBase64(strUserPass64);
137 
138     if (strUserPass.find(':') != std::string::npos)
139         strAuthUsernameOut = strUserPass.substr(0, strUserPass.find(':'));
140 
141     //Check if authorized under single-user field
142     if (TimingResistantEqual(strUserPass, strRPCUserColonPass)) {
143         return true;
144     }
145     return multiUserAuthorized(strUserPass);
146 }
147 
HTTPReq_JSONRPC(HTTPRequest * req,const std::string &)148 static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &)
149 {
150     // JSONRPC handles only POST
151     if (req->GetRequestMethod() != HTTPRequest::POST) {
152         req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests");
153         return false;
154     }
155     // Check authorization
156     std::pair<bool, std::string> authHeader = req->GetHeader("authorization");
157     if (!authHeader.first) {
158         req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
159         req->WriteReply(HTTP_UNAUTHORIZED);
160         return false;
161     }
162 
163     JSONRPCRequest jreq;
164     jreq.peerAddr = req->GetPeer().ToString();
165     if (!RPCAuthorized(authHeader.second, jreq.authUser)) {
166         LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", jreq.peerAddr);
167 
168         /* Deter brute-forcing
169            If this results in a DoS the user really
170            shouldn't have their RPC port exposed. */
171         MilliSleep(250);
172 
173         req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
174         req->WriteReply(HTTP_UNAUTHORIZED);
175         return false;
176     }
177 
178     try {
179         // Parse request
180         UniValue valRequest;
181         if (!valRequest.read(req->ReadBody()))
182             throw JSONRPCError(RPC_PARSE_ERROR, "Parse error");
183 
184         // Set the URI
185         jreq.URI = req->GetURI();
186 
187         std::string strReply;
188         // singleton request
189         if (valRequest.isObject()) {
190             jreq.parse(valRequest);
191 
192             UniValue result = tableRPC.execute(jreq);
193 
194             // Send reply
195             strReply = JSONRPCReply(result, NullUniValue, jreq.id);
196 
197         // array of requests
198         } else if (valRequest.isArray())
199             strReply = JSONRPCExecBatch(jreq, valRequest.get_array());
200         else
201             throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");
202 
203         req->WriteHeader("Content-Type", "application/json");
204         req->WriteReply(HTTP_OK, strReply);
205     } catch (const UniValue& objError) {
206         JSONErrorReply(req, objError, jreq.id);
207         return false;
208     } catch (const std::exception& e) {
209         JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
210         return false;
211     }
212     return true;
213 }
214 
InitRPCAuthentication()215 static bool InitRPCAuthentication()
216 {
217     if (gArgs.GetArg("-rpcpassword", "") == "")
218     {
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);
224             return false;
225         }
226     } else {
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/rpcauth for rpcauth auth generation.\n");
228         strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
229     }
230     if (gArgs.GetArg("-rpcauth","") != "")
231     {
232         LogPrintf("Using rpcauth authentication.\n");
233     }
234     return true;
235 }
236 
StartHTTPRPC()237 bool StartHTTPRPC()
238 {
239     LogPrint(BCLog::RPC, "Starting HTTP RPC server\n");
240     if (!InitRPCAuthentication())
241         return false;
242 
243     RegisterHTTPHandler("/", true, HTTPReq_JSONRPC);
244     if (g_wallet_init_interface.HasWalletSupport()) {
245         RegisterHTTPHandler("/wallet/", false, HTTPReq_JSONRPC);
246     }
247     struct event_base* eventBase = EventBase();
248     assert(eventBase);
249     httpRPCTimerInterface = MakeUnique<HTTPRPCTimerInterface>(eventBase);
250     RPCSetTimerInterface(httpRPCTimerInterface.get());
251     return true;
252 }
253 
InterruptHTTPRPC()254 void InterruptHTTPRPC()
255 {
256     LogPrint(BCLog::RPC, "Interrupting HTTP RPC server\n");
257 }
258 
StopHTTPRPC()259 void StopHTTPRPC()
260 {
261     LogPrint(BCLog::RPC, "Stopping HTTP RPC server\n");
262     UnregisterHTTPHandler("/", true);
263     if (g_wallet_init_interface.HasWalletSupport()) {
264         UnregisterHTTPHandler("/wallet/", false);
265     }
266     if (httpRPCTimerInterface) {
267         RPCUnsetTimerInterface(httpRPCTimerInterface.get());
268         httpRPCTimerInterface.reset();
269     }
270 }
271