]>
Commit | Line | Data |
---|---|---|
afd64f76 WL |
1 | #include "httprpc.h" |
2 | ||
3 | #include "base58.h" | |
4 | #include "chainparams.h" | |
5 | #include "httpserver.h" | |
6 | #include "rpcprotocol.h" | |
7 | #include "rpcserver.h" | |
8 | #include "random.h" | |
9 | #include "sync.h" | |
10 | #include "util.h" | |
11 | #include "utilstrencodings.h" | |
12 | #include "ui_interface.h" | |
13 | ||
14 | #include <boost/algorithm/string.hpp> // boost::trim | |
15 | ||
b5dc10bc | 16 | // WWW-Authenticate to present with 401 Unauthorized response |
17 | static const char *WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\""; | |
18 | ||
afd64f76 WL |
19 | /** Simple one-shot callback timer to be used by the RPC mechanism to e.g. |
20 | * re-lock the wellet. | |
21 | */ | |
22 | class HTTPRPCTimer : public RPCTimerBase | |
23 | { | |
24 | public: | |
858afa1a WL |
25 | HTTPRPCTimer(struct event_base* eventBase, boost::function<void(void)>& func, int64_t millis) : |
26 | ev(eventBase, false, func) | |
afd64f76 | 27 | { |
858afa1a WL |
28 | struct timeval tv; |
29 | tv.tv_sec = millis/1000; | |
30 | tv.tv_usec = (millis%1000)*1000; | |
afd64f76 WL |
31 | ev.trigger(&tv); |
32 | } | |
33 | private: | |
34 | HTTPEvent ev; | |
afd64f76 WL |
35 | }; |
36 | ||
37 | class HTTPRPCTimerInterface : public RPCTimerInterface | |
38 | { | |
39 | public: | |
40 | HTTPRPCTimerInterface(struct event_base* base) : base(base) | |
41 | { | |
42 | } | |
43 | const char* Name() | |
44 | { | |
45 | return "HTTP"; | |
46 | } | |
858afa1a | 47 | RPCTimerBase* NewTimer(boost::function<void(void)>& func, int64_t millis) |
afd64f76 | 48 | { |
858afa1a | 49 | return new HTTPRPCTimer(base, func, millis); |
afd64f76 WL |
50 | } |
51 | private: | |
52 | struct event_base* base; | |
53 | }; | |
54 | ||
55 | ||
56 | /* Pre-base64-encoded authentication token */ | |
57 | static std::string strRPCUserColonPass; | |
58 | /* Stored RPC timer interface (for unregistration) */ | |
59 | static HTTPRPCTimerInterface* httpRPCTimerInterface = 0; | |
60 | ||
61 | static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id) | |
62 | { | |
63 | // Send error reply from json-rpc error object | |
64 | int nStatus = HTTP_INTERNAL_SERVER_ERROR; | |
65 | int code = find_value(objError, "code").get_int(); | |
66 | ||
67 | if (code == RPC_INVALID_REQUEST) | |
68 | nStatus = HTTP_BAD_REQUEST; | |
69 | else if (code == RPC_METHOD_NOT_FOUND) | |
70 | nStatus = HTTP_NOT_FOUND; | |
71 | ||
72 | std::string strReply = JSONRPCReply(NullUniValue, objError, id); | |
73 | ||
74 | req->WriteHeader("Content-Type", "application/json"); | |
75 | req->WriteReply(nStatus, strReply); | |
76 | } | |
77 | ||
78 | static bool RPCAuthorized(const std::string& strAuth) | |
79 | { | |
80 | if (strRPCUserColonPass.empty()) // Belt-and-suspenders measure if InitRPCAuthentication was not called | |
81 | return false; | |
82 | if (strAuth.substr(0, 6) != "Basic ") | |
83 | return false; | |
84 | std::string strUserPass64 = strAuth.substr(6); | |
85 | boost::trim(strUserPass64); | |
86 | std::string strUserPass = DecodeBase64(strUserPass64); | |
87 | return TimingResistantEqual(strUserPass, strRPCUserColonPass); | |
88 | } | |
89 | ||
90 | static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &) | |
91 | { | |
92 | // JSONRPC handles only POST | |
93 | if (req->GetRequestMethod() != HTTPRequest::POST) { | |
94 | req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests"); | |
95 | return false; | |
96 | } | |
97 | // Check authorization | |
98 | std::pair<bool, std::string> authHeader = req->GetHeader("authorization"); | |
99 | if (!authHeader.first) { | |
b5dc10bc | 100 | req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); |
afd64f76 WL |
101 | req->WriteReply(HTTP_UNAUTHORIZED); |
102 | return false; | |
103 | } | |
104 | ||
105 | if (!RPCAuthorized(authHeader.second)) { | |
106 | LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", req->GetPeer().ToString()); | |
107 | ||
108 | /* Deter brute-forcing | |
109 | If this results in a DoS the user really | |
110 | shouldn't have their RPC port exposed. */ | |
111 | MilliSleep(250); | |
112 | ||
b5dc10bc | 113 | req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); |
afd64f76 WL |
114 | req->WriteReply(HTTP_UNAUTHORIZED); |
115 | return false; | |
116 | } | |
117 | ||
118 | JSONRequest jreq; | |
119 | try { | |
120 | // Parse request | |
121 | UniValue valRequest; | |
122 | if (!valRequest.read(req->ReadBody())) | |
123 | throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); | |
124 | ||
125 | std::string strReply; | |
126 | // singleton request | |
127 | if (valRequest.isObject()) { | |
128 | jreq.parse(valRequest); | |
129 | ||
130 | UniValue result = tableRPC.execute(jreq.strMethod, jreq.params); | |
131 | ||
132 | // Send reply | |
133 | strReply = JSONRPCReply(result, NullUniValue, jreq.id); | |
134 | ||
135 | // array of requests | |
136 | } else if (valRequest.isArray()) | |
137 | strReply = JSONRPCExecBatch(valRequest.get_array()); | |
138 | else | |
139 | throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); | |
140 | ||
141 | req->WriteHeader("Content-Type", "application/json"); | |
142 | req->WriteReply(HTTP_OK, strReply); | |
143 | } catch (const UniValue& objError) { | |
144 | JSONErrorReply(req, objError, jreq.id); | |
145 | return false; | |
146 | } catch (const std::exception& e) { | |
147 | JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); | |
148 | return false; | |
149 | } | |
150 | return true; | |
151 | } | |
152 | ||
153 | static bool InitRPCAuthentication() | |
154 | { | |
155 | if (mapArgs["-rpcpassword"] == "") | |
156 | { | |
157 | LogPrintf("No rpcpassword set - using random cookie authentication\n"); | |
158 | if (!GenerateAuthCookie(&strRPCUserColonPass)) { | |
159 | uiInterface.ThreadSafeMessageBox( | |
160 | _("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode | |
161 | "", CClientUIInterface::MSG_ERROR); | |
162 | return false; | |
163 | } | |
164 | } else { | |
165 | strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; | |
166 | } | |
167 | return true; | |
168 | } | |
169 | ||
170 | bool StartHTTPRPC() | |
171 | { | |
172 | LogPrint("rpc", "Starting HTTP RPC server\n"); | |
173 | if (!InitRPCAuthentication()) | |
174 | return false; | |
175 | ||
176 | RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); | |
177 | ||
178 | assert(EventBase()); | |
179 | httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase()); | |
180 | RPCRegisterTimerInterface(httpRPCTimerInterface); | |
181 | return true; | |
182 | } | |
183 | ||
184 | void InterruptHTTPRPC() | |
185 | { | |
186 | LogPrint("rpc", "Interrupting HTTP RPC server\n"); | |
187 | } | |
188 | ||
189 | void StopHTTPRPC() | |
190 | { | |
191 | LogPrint("rpc", "Stopping HTTP RPC server\n"); | |
192 | UnregisterHTTPHandler("/", true); | |
193 | if (httpRPCTimerInterface) { | |
194 | RPCUnregisterTimerInterface(httpRPCTimerInterface); | |
195 | delete httpRPCTimerInterface; | |
196 | httpRPCTimerInterface = 0; | |
197 | } | |
198 | } |