]>
Commit | Line | Data |
---|---|---|
afd64f76 WL |
1 | #include "httprpc.h" |
2 | ||
afd64f76 WL |
3 | #include "chainparams.h" |
4 | #include "httpserver.h" | |
3d31e09c | 5 | #include "key_io.h" |
4519a766 DC |
6 | #include "rpc/protocol.h" |
7 | #include "rpc/server.h" | |
afd64f76 WL |
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 | 19 | /** Simple one-shot callback timer to be used by the RPC mechanism to e.g. |
c938fb1f | 20 | * re-lock the wallet. |
afd64f76 WL |
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 | ||
578a9891 | 105 | if (!RPCAuthorized(authHeader.second)) { |
afd64f76 WL |
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); | |
59d405e8 | 129 | |
130 | if (!RPCAuthorized(authHeader.second)) { | |
131 | LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", req->GetPeer().ToString()); | |
132 | MilliSleep(250); | |
133 | ||
134 | req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA); | |
135 | req->WriteReply(HTTP_UNAUTHORIZED); | |
136 | return false; | |
137 | } | |
afd64f76 | 138 | |
b90dccec | 139 | extern bool printoutAPI; |
140 | if (printoutAPI == true) | |
141 | { | |
375195f3 | 142 | printf("%s %s\n", jreq.strMethod.c_str(), jreq.params.write().c_str()); |
b90dccec | 143 | } |
144 | ||
afd64f76 WL |
145 | UniValue result = tableRPC.execute(jreq.strMethod, jreq.params); |
146 | ||
147 | // Send reply | |
148 | strReply = JSONRPCReply(result, NullUniValue, jreq.id); | |
149 | ||
150 | // array of requests | |
151 | } else if (valRequest.isArray()) | |
152 | strReply = JSONRPCExecBatch(valRequest.get_array()); | |
153 | else | |
154 | throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); | |
155 | ||
156 | req->WriteHeader("Content-Type", "application/json"); | |
157 | req->WriteReply(HTTP_OK, strReply); | |
158 | } catch (const UniValue& objError) { | |
159 | JSONErrorReply(req, objError, jreq.id); | |
160 | return false; | |
161 | } catch (const std::exception& e) { | |
162 | JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); | |
163 | return false; | |
164 | } | |
165 | return true; | |
166 | } | |
167 | ||
168 | static bool InitRPCAuthentication() | |
169 | { | |
170 | if (mapArgs["-rpcpassword"] == "") | |
171 | { | |
172 | LogPrintf("No rpcpassword set - using random cookie authentication\n"); | |
173 | if (!GenerateAuthCookie(&strRPCUserColonPass)) { | |
174 | uiInterface.ThreadSafeMessageBox( | |
175 | _("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode | |
176 | "", CClientUIInterface::MSG_ERROR); | |
177 | return false; | |
178 | } | |
179 | } else { | |
180 | strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; | |
181 | } | |
182 | return true; | |
183 | } | |
184 | ||
185 | bool StartHTTPRPC() | |
186 | { | |
187 | LogPrint("rpc", "Starting HTTP RPC server\n"); | |
188 | if (!InitRPCAuthentication()) | |
189 | return false; | |
190 | ||
191 | RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); | |
192 | ||
193 | assert(EventBase()); | |
194 | httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase()); | |
195 | RPCRegisterTimerInterface(httpRPCTimerInterface); | |
196 | return true; | |
197 | } | |
198 | ||
199 | void InterruptHTTPRPC() | |
200 | { | |
201 | LogPrint("rpc", "Interrupting HTTP RPC server\n"); | |
202 | } | |
203 | ||
204 | void StopHTTPRPC() | |
205 | { | |
206 | LogPrint("rpc", "Stopping HTTP RPC server\n"); | |
207 | UnregisterHTTPHandler("/", true); | |
208 | if (httpRPCTimerInterface) { | |
209 | RPCUnregisterTimerInterface(httpRPCTimerInterface); | |
210 | delete httpRPCTimerInterface; | |
211 | httpRPCTimerInterface = 0; | |
212 | } | |
213 | } |