]>
Commit | Line | Data |
---|---|---|
69d605f4 | 1 | // Copyright (c) 2010 Satoshi Nakamoto |
88216419 | 2 | // Copyright (c) 2009-2012 The Bitcoin developers |
69d605f4 | 3 | // Distributed under the MIT/X11 software license, see the accompanying |
3a25a2b9 | 4 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
69d605f4 | 5 | |
0eeb4f5d | 6 | #include "init.h" |
e3bc5698 JG |
7 | #include "util.h" |
8 | #include "sync.h" | |
ed6d0b5f | 9 | #include "ui_interface.h" |
10254401 | 10 | #include "base58.h" |
e46704dd | 11 | #include "bitcoinrpc.h" |
3731f578 | 12 | #include "db.h" |
ed6d0b5f | 13 | |
69d605f4 | 14 | #include <boost/asio.hpp> |
c1ecab81 | 15 | #include <boost/asio/ip/v6_only.hpp> |
914dc012 | 16 | #include <boost/bind.hpp> |
95d888a6 | 17 | #include <boost/filesystem.hpp> |
a0780ba0 | 18 | #include <boost/foreach.hpp> |
69d605f4 WL |
19 | #include <boost/iostreams/concepts.hpp> |
20 | #include <boost/iostreams/stream.hpp> | |
0eeb4f5d | 21 | #include <boost/algorithm/string.hpp> |
30ab2c9c | 22 | #include <boost/lexical_cast.hpp> |
9247134e | 23 | #include <boost/asio/ssl.hpp> |
0eeb4f5d | 24 | #include <boost/filesystem/fstream.hpp> |
914dc012 | 25 | #include <boost/shared_ptr.hpp> |
a0780ba0 | 26 | #include <list> |
5ce4c2a2 | 27 | |
69d605f4 WL |
28 | using namespace std; |
29 | using namespace boost; | |
30 | using namespace boost::asio; | |
31 | using namespace json_spirit; | |
32 | ||
33 | void ThreadRPCServer2(void* parg); | |
69d605f4 | 34 | |
f81ce5bd GA |
35 | static std::string strRPCUserColonPass; |
36 | ||
74335bd3 LD |
37 | const Object emptyobj; |
38 | ||
e9205293 DJS |
39 | void ThreadRPCServer3(void* parg); |
40 | ||
b202d430 | 41 | static inline unsigned short GetDefaultRPCPort() |
42 | { | |
43 | return GetBoolArg("-testnet", false) ? 18332 : 8332; | |
44 | } | |
45 | ||
69d605f4 WL |
46 | Object JSONRPCError(int code, const string& message) |
47 | { | |
48 | Object error; | |
49 | error.push_back(Pair("code", code)); | |
50 | error.push_back(Pair("message", message)); | |
51 | return error; | |
52 | } | |
53 | ||
899d373b | 54 | void RPCTypeCheck(const Array& params, |
cc6dfd1f GA |
55 | const list<Value_type>& typesExpected, |
56 | bool fAllowNull) | |
899d373b | 57 | { |
dab9fa7f | 58 | unsigned int i = 0; |
899d373b GA |
59 | BOOST_FOREACH(Value_type t, typesExpected) |
60 | { | |
61 | if (params.size() <= i) | |
62 | break; | |
63 | ||
cc6dfd1f GA |
64 | const Value& v = params[i]; |
65 | if (!((v.type() == t) || (fAllowNull && (v.type() == null_type)))) | |
899d373b GA |
66 | { |
67 | string err = strprintf("Expected type %s, got %s", | |
68 | Value_type_name[t], Value_type_name[v.type()]); | |
738835d7 | 69 | throw JSONRPCError(RPC_TYPE_ERROR, err); |
899d373b GA |
70 | } |
71 | i++; | |
72 | } | |
73 | } | |
74 | ||
75 | void RPCTypeCheck(const Object& o, | |
cc6dfd1f GA |
76 | const map<string, Value_type>& typesExpected, |
77 | bool fAllowNull) | |
899d373b GA |
78 | { |
79 | BOOST_FOREACH(const PAIRTYPE(string, Value_type)& t, typesExpected) | |
80 | { | |
81 | const Value& v = find_value(o, t.first); | |
cc6dfd1f | 82 | if (!fAllowNull && v.type() == null_type) |
738835d7 | 83 | throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing %s", t.first.c_str())); |
cc6dfd1f GA |
84 | |
85 | if (!((v.type() == t.second) || (fAllowNull && (v.type() == null_type)))) | |
899d373b GA |
86 | { |
87 | string err = strprintf("Expected type %s for %s, got %s", | |
88 | Value_type_name[t.second], t.first.c_str(), Value_type_name[v.type()]); | |
738835d7 | 89 | throw JSONRPCError(RPC_TYPE_ERROR, err); |
899d373b GA |
90 | } |
91 | } | |
92 | } | |
93 | ||
bde280b9 | 94 | int64 AmountFromValue(const Value& value) |
69d605f4 WL |
95 | { |
96 | double dAmount = value.get_real(); | |
97 | if (dAmount <= 0.0 || dAmount > 21000000.0) | |
738835d7 | 98 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); |
bde280b9 | 99 | int64 nAmount = roundint64(dAmount * COIN); |
69d605f4 | 100 | if (!MoneyRange(nAmount)) |
738835d7 | 101 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); |
69d605f4 WL |
102 | return nAmount; |
103 | } | |
104 | ||
bde280b9 | 105 | Value ValueFromAmount(int64 amount) |
69d605f4 WL |
106 | { |
107 | return (double)amount / (double)COIN; | |
108 | } | |
109 | ||
bdab0cf5 | 110 | std::string HexBits(unsigned int nBits) |
34f87889 LD |
111 | { |
112 | union { | |
113 | int32_t nBits; | |
114 | char cBits[4]; | |
115 | } uBits; | |
116 | uBits.nBits = htonl((int32_t)nBits); | |
117 | return HexStr(BEGIN(uBits.cBits), END(uBits.cBits)); | |
118 | } | |
119 | ||
69d605f4 | 120 | |
74335bd3 | 121 | |
69d605f4 WL |
122 | /// |
123 | /// Note: This interface may still be subject to change. | |
124 | /// | |
125 | ||
9862229d | 126 | string CRPCTable::help(string strCommand) const |
69d605f4 | 127 | { |
69d605f4 WL |
128 | string strRet; |
129 | set<rpcfn_type> setDone; | |
9862229d | 130 | for (map<string, const CRPCCommand*>::const_iterator mi = mapCommands.begin(); mi != mapCommands.end(); ++mi) |
69d605f4 | 131 | { |
9862229d | 132 | const CRPCCommand *pcmd = mi->second; |
dc42bf52 | 133 | string strMethod = mi->first; |
69d605f4 | 134 | // We already filter duplicates, but these deprecated screw up the sort order |
47f48a65 | 135 | if (strMethod.find("label") != string::npos) |
69d605f4 WL |
136 | continue; |
137 | if (strCommand != "" && strMethod != strCommand) | |
138 | continue; | |
139 | try | |
140 | { | |
141 | Array params; | |
dc42bf52 | 142 | rpcfn_type pfn = pcmd->actor; |
69d605f4 WL |
143 | if (setDone.insert(pfn).second) |
144 | (*pfn)(params, true); | |
145 | } | |
146 | catch (std::exception& e) | |
147 | { | |
148 | // Help text is returned in an exception | |
149 | string strHelp = string(e.what()); | |
150 | if (strCommand == "") | |
ab9dc75a | 151 | if (strHelp.find('\n') != string::npos) |
69d605f4 WL |
152 | strHelp = strHelp.substr(0, strHelp.find('\n')); |
153 | strRet += strHelp + "\n"; | |
154 | } | |
155 | } | |
156 | if (strRet == "") | |
157 | strRet = strprintf("help: unknown command: %s\n", strCommand.c_str()); | |
158 | strRet = strRet.substr(0,strRet.size()-1); | |
159 | return strRet; | |
160 | } | |
161 | ||
9862229d PW |
162 | Value help(const Array& params, bool fHelp) |
163 | { | |
164 | if (fHelp || params.size() > 1) | |
165 | throw runtime_error( | |
166 | "help [command]\n" | |
167 | "List commands, or get help for a command."); | |
168 | ||
169 | string strCommand; | |
170 | if (params.size() > 0) | |
171 | strCommand = params[0].get_str(); | |
172 | ||
173 | return tableRPC.help(strCommand); | |
174 | } | |
175 | ||
69d605f4 WL |
176 | |
177 | Value stop(const Array& params, bool fHelp) | |
178 | { | |
92467073 | 179 | // Accept the deprecated and ignored 'detach´ boolean argument |
3731f578 | 180 | if (fHelp || params.size() > 1) |
69d605f4 | 181 | throw runtime_error( |
92467073 PW |
182 | "stop\n" |
183 | "Stop Bitcoin server."); | |
69d605f4 | 184 | // Shutdown will take long enough that the response should get back |
9247134e | 185 | StartShutdown(); |
ff0ee876 | 186 | return "Bitcoin server stopping"; |
69d605f4 WL |
187 | } |
188 | ||
189 | ||
69d605f4 WL |
190 | |
191 | // | |
192 | // Call Table | |
193 | // | |
194 | ||
dc42bf52 | 195 | |
e46704dd | 196 | static const CRPCCommand vRPCCommands[] = |
0e1d3551 JG |
197 | { // name function safemd unlocked |
198 | // ------------------------ ----------------------- ------ -------- | |
199 | { "help", &help, true, true }, | |
200 | { "stop", &stop, true, true }, | |
201 | { "getblockcount", &getblockcount, true, false }, | |
202 | { "getconnectioncount", &getconnectioncount, true, false }, | |
203 | { "getpeerinfo", &getpeerinfo, true, false }, | |
204 | { "getdifficulty", &getdifficulty, true, false }, | |
205 | { "getgenerate", &getgenerate, true, false }, | |
206 | { "setgenerate", &setgenerate, true, false }, | |
207 | { "gethashespersec", &gethashespersec, true, false }, | |
208 | { "getinfo", &getinfo, true, false }, | |
209 | { "getmininginfo", &getmininginfo, true, false }, | |
210 | { "getnewaddress", &getnewaddress, true, false }, | |
211 | { "getaccountaddress", &getaccountaddress, true, false }, | |
212 | { "setaccount", &setaccount, true, false }, | |
213 | { "getaccount", &getaccount, false, false }, | |
214 | { "getaddressesbyaccount", &getaddressesbyaccount, true, false }, | |
215 | { "sendtoaddress", &sendtoaddress, false, false }, | |
216 | { "getreceivedbyaddress", &getreceivedbyaddress, false, false }, | |
217 | { "getreceivedbyaccount", &getreceivedbyaccount, false, false }, | |
218 | { "listreceivedbyaddress", &listreceivedbyaddress, false, false }, | |
219 | { "listreceivedbyaccount", &listreceivedbyaccount, false, false }, | |
220 | { "backupwallet", &backupwallet, true, false }, | |
221 | { "keypoolrefill", &keypoolrefill, true, false }, | |
222 | { "walletpassphrase", &walletpassphrase, true, false }, | |
223 | { "walletpassphrasechange", &walletpassphrasechange, false, false }, | |
224 | { "walletlock", &walletlock, true, false }, | |
225 | { "encryptwallet", &encryptwallet, false, false }, | |
226 | { "validateaddress", &validateaddress, true, false }, | |
227 | { "getbalance", &getbalance, false, false }, | |
228 | { "move", &movecmd, false, false }, | |
229 | { "sendfrom", &sendfrom, false, false }, | |
230 | { "sendmany", &sendmany, false, false }, | |
231 | { "addmultisigaddress", &addmultisigaddress, false, false }, | |
34226be7 | 232 | { "createmultisig", &createmultisig, true, true }, |
0e1d3551 JG |
233 | { "getrawmempool", &getrawmempool, true, false }, |
234 | { "getblock", &getblock, false, false }, | |
235 | { "getblockhash", &getblockhash, false, false }, | |
236 | { "gettransaction", &gettransaction, false, false }, | |
237 | { "listtransactions", &listtransactions, false, false }, | |
22dfd735 | 238 | { "listaddressgroupings", &listaddressgroupings, false, false }, |
0e1d3551 JG |
239 | { "signmessage", &signmessage, false, false }, |
240 | { "verifymessage", &verifymessage, false, false }, | |
241 | { "getwork", &getwork, true, false }, | |
242 | { "listaccounts", &listaccounts, false, false }, | |
243 | { "settxfee", &settxfee, false, false }, | |
244 | { "getblocktemplate", &getblocktemplate, true, false }, | |
245 | { "submitblock", &submitblock, false, false }, | |
246 | { "listsinceblock", &listsinceblock, false, false }, | |
247 | { "dumpprivkey", &dumpprivkey, false, false }, | |
248 | { "importprivkey", &importprivkey, false, false }, | |
249 | { "listunspent", &listunspent, false, false }, | |
250 | { "getrawtransaction", &getrawtransaction, false, false }, | |
251 | { "createrawtransaction", &createrawtransaction, false, false }, | |
252 | { "decoderawtransaction", &decoderawtransaction, false, false }, | |
253 | { "signrawtransaction", &signrawtransaction, false, false }, | |
254 | { "sendrawtransaction", &sendrawtransaction, false, false }, | |
beeb5761 PW |
255 | { "gettxoutsetinfo", &gettxoutsetinfo, true, false }, |
256 | { "gettxout", &gettxout, true, false }, | |
fdbb537d JG |
257 | { "lockunspent", &lockunspent, false, false }, |
258 | { "listlockunspent", &listlockunspent, false, false }, | |
69d605f4 | 259 | }; |
69d605f4 | 260 | |
9862229d | 261 | CRPCTable::CRPCTable() |
dc42bf52 | 262 | { |
dc42bf52 JG |
263 | unsigned int vcidx; |
264 | for (vcidx = 0; vcidx < (sizeof(vRPCCommands) / sizeof(vRPCCommands[0])); vcidx++) | |
265 | { | |
e46704dd | 266 | const CRPCCommand *pcmd; |
69d605f4 | 267 | |
dc42bf52 JG |
268 | pcmd = &vRPCCommands[vcidx]; |
269 | mapCommands[pcmd->name] = pcmd; | |
270 | } | |
271 | } | |
69d605f4 | 272 | |
9862229d PW |
273 | const CRPCCommand *CRPCTable::operator[](string name) const |
274 | { | |
275 | map<string, const CRPCCommand*>::const_iterator it = mapCommands.find(name); | |
276 | if (it == mapCommands.end()) | |
277 | return NULL; | |
278 | return (*it).second; | |
279 | } | |
69d605f4 WL |
280 | |
281 | // | |
282 | // HTTP protocol | |
283 | // | |
284 | // This ain't Apache. We're just using HTTP header for the length field | |
285 | // and to be compatible with other JSON-RPC implementations. | |
286 | // | |
287 | ||
288 | string HTTPPost(const string& strMsg, const map<string,string>& mapRequestHeaders) | |
289 | { | |
290 | ostringstream s; | |
291 | s << "POST / HTTP/1.1\r\n" | |
292 | << "User-Agent: bitcoin-json-rpc/" << FormatFullVersion() << "\r\n" | |
293 | << "Host: 127.0.0.1\r\n" | |
294 | << "Content-Type: application/json\r\n" | |
295 | << "Content-Length: " << strMsg.size() << "\r\n" | |
3552497a | 296 | << "Connection: close\r\n" |
69d605f4 WL |
297 | << "Accept: application/json\r\n"; |
298 | BOOST_FOREACH(const PAIRTYPE(string, string)& item, mapRequestHeaders) | |
299 | s << item.first << ": " << item.second << "\r\n"; | |
300 | s << "\r\n" << strMsg; | |
301 | ||
302 | return s.str(); | |
303 | } | |
304 | ||
305 | string rfc1123Time() | |
306 | { | |
307 | char buffer[64]; | |
308 | time_t now; | |
309 | time(&now); | |
310 | struct tm* now_gmt = gmtime(&now); | |
311 | string locale(setlocale(LC_TIME, NULL)); | |
814efd6f | 312 | setlocale(LC_TIME, "C"); // we want POSIX (aka "C") weekday/month strings |
69d605f4 WL |
313 | strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S +0000", now_gmt); |
314 | setlocale(LC_TIME, locale.c_str()); | |
315 | return string(buffer); | |
316 | } | |
317 | ||
96c52695 | 318 | static string HTTPReply(int nStatus, const string& strMsg, bool keepalive) |
69d605f4 | 319 | { |
285746d3 | 320 | if (nStatus == HTTP_UNAUTHORIZED) |
69d605f4 WL |
321 | return strprintf("HTTP/1.0 401 Authorization Required\r\n" |
322 | "Date: %s\r\n" | |
323 | "Server: bitcoin-json-rpc/%s\r\n" | |
324 | "WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n" | |
325 | "Content-Type: text/html\r\n" | |
326 | "Content-Length: 296\r\n" | |
327 | "\r\n" | |
328 | "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n" | |
329 | "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n" | |
330 | "<HTML>\r\n" | |
331 | "<HEAD>\r\n" | |
332 | "<TITLE>Error</TITLE>\r\n" | |
333 | "<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n" | |
334 | "</HEAD>\r\n" | |
335 | "<BODY><H1>401 Unauthorized.</H1></BODY>\r\n" | |
336 | "</HTML>\r\n", rfc1123Time().c_str(), FormatFullVersion().c_str()); | |
ae81b82f | 337 | const char *cStatus; |
285746d3 WL |
338 | if (nStatus == HTTP_OK) cStatus = "OK"; |
339 | else if (nStatus == HTTP_BAD_REQUEST) cStatus = "Bad Request"; | |
340 | else if (nStatus == HTTP_FORBIDDEN) cStatus = "Forbidden"; | |
341 | else if (nStatus == HTTP_NOT_FOUND) cStatus = "Not Found"; | |
342 | else if (nStatus == HTTP_INTERNAL_SERVER_ERROR) cStatus = "Internal Server Error"; | |
ae81b82f | 343 | else cStatus = ""; |
69d605f4 WL |
344 | return strprintf( |
345 | "HTTP/1.1 %d %s\r\n" | |
346 | "Date: %s\r\n" | |
96c52695 | 347 | "Connection: %s\r\n" |
d210f4f5 | 348 | "Content-Length: %"PRIszu"\r\n" |
69d605f4 WL |
349 | "Content-Type: application/json\r\n" |
350 | "Server: bitcoin-json-rpc/%s\r\n" | |
351 | "\r\n" | |
352 | "%s", | |
353 | nStatus, | |
ae81b82f | 354 | cStatus, |
69d605f4 | 355 | rfc1123Time().c_str(), |
96c52695 | 356 | keepalive ? "keep-alive" : "close", |
69d605f4 WL |
357 | strMsg.size(), |
358 | FormatFullVersion().c_str(), | |
359 | strMsg.c_str()); | |
360 | } | |
361 | ||
fcf234fc JG |
362 | bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto, |
363 | string& http_method, string& http_uri) | |
364 | { | |
365 | string str; | |
366 | getline(stream, str); | |
367 | ||
368 | // HTTP request line is space-delimited | |
369 | vector<string> vWords; | |
370 | boost::split(vWords, str, boost::is_any_of(" ")); | |
371 | if (vWords.size() < 2) | |
372 | return false; | |
373 | ||
374 | // HTTP methods permitted: GET, POST | |
375 | http_method = vWords[0]; | |
376 | if (http_method != "GET" && http_method != "POST") | |
377 | return false; | |
378 | ||
379 | // HTTP URI must be an absolute path, relative to current host | |
380 | http_uri = vWords[1]; | |
381 | if (http_uri.size() == 0 || http_uri[0] != '/') | |
382 | return false; | |
383 | ||
384 | // parse proto, if present | |
385 | string strProto = ""; | |
386 | if (vWords.size() > 2) | |
387 | strProto = vWords[2]; | |
388 | ||
389 | proto = 0; | |
390 | const char *ver = strstr(strProto.c_str(), "HTTP/1."); | |
391 | if (ver != NULL) | |
392 | proto = atoi(ver+7); | |
393 | ||
394 | return true; | |
395 | } | |
396 | ||
96c52695 | 397 | int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto) |
69d605f4 WL |
398 | { |
399 | string str; | |
400 | getline(stream, str); | |
401 | vector<string> vWords; | |
402 | boost::split(vWords, str, boost::is_any_of(" ")); | |
403 | if (vWords.size() < 2) | |
285746d3 | 404 | return HTTP_INTERNAL_SERVER_ERROR; |
96c52695 DJS |
405 | proto = 0; |
406 | const char *ver = strstr(str.c_str(), "HTTP/1."); | |
407 | if (ver != NULL) | |
408 | proto = atoi(ver+7); | |
69d605f4 WL |
409 | return atoi(vWords[1].c_str()); |
410 | } | |
411 | ||
2306dc4b | 412 | int ReadHTTPHeaders(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet) |
69d605f4 WL |
413 | { |
414 | int nLen = 0; | |
415 | loop | |
416 | { | |
417 | string str; | |
418 | std::getline(stream, str); | |
419 | if (str.empty() || str == "\r") | |
420 | break; | |
421 | string::size_type nColon = str.find(":"); | |
422 | if (nColon != string::npos) | |
423 | { | |
424 | string strHeader = str.substr(0, nColon); | |
425 | boost::trim(strHeader); | |
426 | boost::to_lower(strHeader); | |
427 | string strValue = str.substr(nColon+1); | |
428 | boost::trim(strValue); | |
429 | mapHeadersRet[strHeader] = strValue; | |
430 | if (strHeader == "content-length") | |
431 | nLen = atoi(strValue.c_str()); | |
432 | } | |
433 | } | |
434 | return nLen; | |
435 | } | |
436 | ||
2306dc4b JG |
437 | int ReadHTTPMessage(std::basic_istream<char>& stream, map<string, |
438 | string>& mapHeadersRet, string& strMessageRet, | |
439 | int nProto) | |
69d605f4 WL |
440 | { |
441 | mapHeadersRet.clear(); | |
442 | strMessageRet = ""; | |
443 | ||
69d605f4 | 444 | // Read header |
2306dc4b | 445 | int nLen = ReadHTTPHeaders(stream, mapHeadersRet); |
1d8c7a95 | 446 | if (nLen < 0 || nLen > (int)MAX_SIZE) |
285746d3 | 447 | return HTTP_INTERNAL_SERVER_ERROR; |
69d605f4 WL |
448 | |
449 | // Read message | |
450 | if (nLen > 0) | |
451 | { | |
452 | vector<char> vch(nLen); | |
453 | stream.read(&vch[0], nLen); | |
454 | strMessageRet = string(vch.begin(), vch.end()); | |
455 | } | |
456 | ||
96c52695 DJS |
457 | string sConHdr = mapHeadersRet["connection"]; |
458 | ||
459 | if ((sConHdr != "close") && (sConHdr != "keep-alive")) | |
460 | { | |
461 | if (nProto >= 1) | |
462 | mapHeadersRet["connection"] = "keep-alive"; | |
463 | else | |
464 | mapHeadersRet["connection"] = "close"; | |
465 | } | |
466 | ||
2306dc4b | 467 | return HTTP_OK; |
69d605f4 WL |
468 | } |
469 | ||
69d605f4 WL |
470 | bool HTTPAuthorized(map<string, string>& mapHeaders) |
471 | { | |
472 | string strAuth = mapHeaders["authorization"]; | |
473 | if (strAuth.substr(0,6) != "Basic ") | |
474 | return false; | |
475 | string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64); | |
476 | string strUserPass = DecodeBase64(strUserPass64); | |
f81ce5bd | 477 | return strUserPass == strRPCUserColonPass; |
69d605f4 WL |
478 | } |
479 | ||
480 | // | |
481 | // JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, | |
482 | // but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were | |
483 | // unspecified (HTTP errors and contents of 'error'). | |
484 | // | |
485 | // 1.0 spec: http://json-rpc.org/wiki/specification | |
486 | // 1.2 spec: http://groups.google.com/group/json-rpc/web/json-rpc-over-http | |
487 | // http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx | |
488 | // | |
489 | ||
490 | string JSONRPCRequest(const string& strMethod, const Array& params, const Value& id) | |
491 | { | |
492 | Object request; | |
493 | request.push_back(Pair("method", strMethod)); | |
494 | request.push_back(Pair("params", params)); | |
495 | request.push_back(Pair("id", id)); | |
496 | return write_string(Value(request), false) + "\n"; | |
497 | } | |
498 | ||
c6494d82 | 499 | Object JSONRPCReplyObj(const Value& result, const Value& error, const Value& id) |
69d605f4 WL |
500 | { |
501 | Object reply; | |
502 | if (error.type() != null_type) | |
503 | reply.push_back(Pair("result", Value::null)); | |
504 | else | |
505 | reply.push_back(Pair("result", result)); | |
506 | reply.push_back(Pair("error", error)); | |
507 | reply.push_back(Pair("id", id)); | |
c6494d82 JG |
508 | return reply; |
509 | } | |
510 | ||
511 | string JSONRPCReply(const Value& result, const Value& error, const Value& id) | |
512 | { | |
513 | Object reply = JSONRPCReplyObj(result, error, id); | |
69d605f4 WL |
514 | return write_string(Value(reply), false) + "\n"; |
515 | } | |
516 | ||
517 | void ErrorReply(std::ostream& stream, const Object& objError, const Value& id) | |
518 | { | |
519 | // Send error reply from json-rpc error object | |
285746d3 | 520 | int nStatus = HTTP_INTERNAL_SERVER_ERROR; |
69d605f4 | 521 | int code = find_value(objError, "code").get_int(); |
285746d3 WL |
522 | if (code == RPC_INVALID_REQUEST) nStatus = HTTP_BAD_REQUEST; |
523 | else if (code == RPC_METHOD_NOT_FOUND) nStatus = HTTP_NOT_FOUND; | |
69d605f4 | 524 | string strReply = JSONRPCReply(Value::null, objError, id); |
96c52695 | 525 | stream << HTTPReply(nStatus, strReply, false) << std::flush; |
69d605f4 WL |
526 | } |
527 | ||
43b6dafa | 528 | bool ClientAllowed(const boost::asio::ip::address& address) |
69d605f4 | 529 | { |
43b6dafa GS |
530 | // Make sure that IPv4-compatible and IPv4-mapped IPv6 addresses are treated as IPv4 addresses |
531 | if (address.is_v6() | |
532 | && (address.to_v6().is_v4_compatible() | |
533 | || address.to_v6().is_v4_mapped())) | |
534 | return ClientAllowed(address.to_v6().to_v4()); | |
535 | ||
536 | if (address == asio::ip::address_v4::loopback() | |
7cc2ceae GS |
537 | || address == asio::ip::address_v6::loopback() |
538 | || (address.is_v4() | |
814efd6f | 539 | // Check whether IPv4 addresses match 127.0.0.0/8 (loopback subnet) |
7cc2ceae | 540 | && (address.to_v4().to_ulong() & 0xff000000) == 0x7f000000)) |
69d605f4 | 541 | return true; |
43b6dafa GS |
542 | |
543 | const string strAddress = address.to_string(); | |
69d605f4 WL |
544 | const vector<string>& vAllow = mapMultiArgs["-rpcallowip"]; |
545 | BOOST_FOREACH(string strAllow, vAllow) | |
546 | if (WildcardMatch(strAddress, strAllow)) | |
547 | return true; | |
548 | return false; | |
549 | } | |
550 | ||
69d605f4 WL |
551 | // |
552 | // IOStream device that speaks SSL but can also speak non-SSL | |
553 | // | |
a0780ba0 | 554 | template <typename Protocol> |
69d605f4 WL |
555 | class SSLIOStreamDevice : public iostreams::device<iostreams::bidirectional> { |
556 | public: | |
a0780ba0 | 557 | SSLIOStreamDevice(asio::ssl::stream<typename Protocol::socket> &streamIn, bool fUseSSLIn) : stream(streamIn) |
69d605f4 WL |
558 | { |
559 | fUseSSL = fUseSSLIn; | |
560 | fNeedHandshake = fUseSSLIn; | |
561 | } | |
562 | ||
563 | void handshake(ssl::stream_base::handshake_type role) | |
564 | { | |
565 | if (!fNeedHandshake) return; | |
566 | fNeedHandshake = false; | |
567 | stream.handshake(role); | |
568 | } | |
569 | std::streamsize read(char* s, std::streamsize n) | |
570 | { | |
571 | handshake(ssl::stream_base::server); // HTTPS servers read first | |
572 | if (fUseSSL) return stream.read_some(asio::buffer(s, n)); | |
573 | return stream.next_layer().read_some(asio::buffer(s, n)); | |
574 | } | |
575 | std::streamsize write(const char* s, std::streamsize n) | |
576 | { | |
577 | handshake(ssl::stream_base::client); // HTTPS clients write first | |
578 | if (fUseSSL) return asio::write(stream, asio::buffer(s, n)); | |
579 | return asio::write(stream.next_layer(), asio::buffer(s, n)); | |
580 | } | |
581 | bool connect(const std::string& server, const std::string& port) | |
582 | { | |
583 | ip::tcp::resolver resolver(stream.get_io_service()); | |
584 | ip::tcp::resolver::query query(server.c_str(), port.c_str()); | |
585 | ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); | |
586 | ip::tcp::resolver::iterator end; | |
587 | boost::system::error_code error = asio::error::host_not_found; | |
588 | while (error && endpoint_iterator != end) | |
589 | { | |
590 | stream.lowest_layer().close(); | |
591 | stream.lowest_layer().connect(*endpoint_iterator++, error); | |
592 | } | |
593 | if (error) | |
594 | return false; | |
595 | return true; | |
596 | } | |
597 | ||
598 | private: | |
599 | bool fNeedHandshake; | |
600 | bool fUseSSL; | |
a0780ba0 | 601 | asio::ssl::stream<typename Protocol::socket>& stream; |
69d605f4 | 602 | }; |
69d605f4 | 603 | |
e9205293 DJS |
604 | class AcceptedConnection |
605 | { | |
a0780ba0 GS |
606 | public: |
607 | virtual ~AcceptedConnection() {} | |
608 | ||
609 | virtual std::iostream& stream() = 0; | |
610 | virtual std::string peer_address_to_string() const = 0; | |
611 | virtual void close() = 0; | |
612 | }; | |
e9205293 | 613 | |
a0780ba0 GS |
614 | template <typename Protocol> |
615 | class AcceptedConnectionImpl : public AcceptedConnection | |
616 | { | |
617 | public: | |
618 | AcceptedConnectionImpl( | |
619 | asio::io_service& io_service, | |
620 | ssl::context &context, | |
621 | bool fUseSSL) : | |
622 | sslStream(io_service, context), | |
623 | _d(sslStream, fUseSSL), | |
624 | _stream(_d) | |
625 | { | |
626 | } | |
627 | ||
628 | virtual std::iostream& stream() | |
629 | { | |
630 | return _stream; | |
631 | } | |
632 | ||
633 | virtual std::string peer_address_to_string() const | |
634 | { | |
635 | return peer.address().to_string(); | |
636 | } | |
e9205293 | 637 | |
a0780ba0 GS |
638 | virtual void close() |
639 | { | |
640 | _stream.close(); | |
641 | } | |
642 | ||
643 | typename Protocol::endpoint peer; | |
644 | asio::ssl::stream<typename Protocol::socket> sslStream; | |
645 | ||
646 | private: | |
647 | SSLIOStreamDevice<Protocol> _d; | |
648 | iostreams::stream< SSLIOStreamDevice<Protocol> > _stream; | |
e9205293 DJS |
649 | }; |
650 | ||
69d605f4 WL |
651 | void ThreadRPCServer(void* parg) |
652 | { | |
96931d6f GS |
653 | // Make this thread recognisable as the RPC listener |
654 | RenameThread("bitcoin-rpclist"); | |
655 | ||
69d605f4 WL |
656 | try |
657 | { | |
e9205293 | 658 | vnThreadsRunning[THREAD_RPCLISTENER]++; |
69d605f4 | 659 | ThreadRPCServer2(parg); |
e9205293 | 660 | vnThreadsRunning[THREAD_RPCLISTENER]--; |
69d605f4 WL |
661 | } |
662 | catch (std::exception& e) { | |
e9205293 | 663 | vnThreadsRunning[THREAD_RPCLISTENER]--; |
69d605f4 WL |
664 | PrintException(&e, "ThreadRPCServer()"); |
665 | } catch (...) { | |
e9205293 | 666 | vnThreadsRunning[THREAD_RPCLISTENER]--; |
69d605f4 WL |
667 | PrintException(NULL, "ThreadRPCServer()"); |
668 | } | |
1d764d63 | 669 | printf("ThreadRPCServer exited\n"); |
69d605f4 WL |
670 | } |
671 | ||
914dc012 | 672 | // Forward declaration required for RPCListen |
a0780ba0 GS |
673 | template <typename Protocol, typename SocketAcceptorService> |
674 | static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, | |
914dc012 GS |
675 | ssl::context& context, |
676 | bool fUseSSL, | |
677 | AcceptedConnection* conn, | |
678 | const boost::system::error_code& error); | |
679 | ||
680 | /** | |
681 | * Sets up I/O resources to accept and handle a new connection. | |
682 | */ | |
a0780ba0 GS |
683 | template <typename Protocol, typename SocketAcceptorService> |
684 | static void RPCListen(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, | |
914dc012 GS |
685 | ssl::context& context, |
686 | const bool fUseSSL) | |
687 | { | |
914dc012 | 688 | // Accept connection |
a0780ba0 | 689 | AcceptedConnectionImpl<Protocol>* conn = new AcceptedConnectionImpl<Protocol>(acceptor->get_io_service(), context, fUseSSL); |
914dc012 GS |
690 | |
691 | acceptor->async_accept( | |
692 | conn->sslStream.lowest_layer(), | |
693 | conn->peer, | |
a0780ba0 | 694 | boost::bind(&RPCAcceptHandler<Protocol, SocketAcceptorService>, |
914dc012 GS |
695 | acceptor, |
696 | boost::ref(context), | |
697 | fUseSSL, | |
698 | conn, | |
699 | boost::asio::placeholders::error)); | |
700 | } | |
701 | ||
702 | /** | |
703 | * Accept and handle incoming connection. | |
704 | */ | |
a0780ba0 GS |
705 | template <typename Protocol, typename SocketAcceptorService> |
706 | static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, | |
914dc012 GS |
707 | ssl::context& context, |
708 | const bool fUseSSL, | |
709 | AcceptedConnection* conn, | |
710 | const boost::system::error_code& error) | |
711 | { | |
712 | vnThreadsRunning[THREAD_RPCLISTENER]++; | |
713 | ||
814efd6f | 714 | // Immediately start accepting new connections, except when we're cancelled or our socket is closed. |
460d8786 | 715 | if (error != asio::error::operation_aborted |
ad25804f GS |
716 | && acceptor->is_open()) |
717 | RPCListen(acceptor, context, fUseSSL); | |
914dc012 | 718 | |
a0780ba0 GS |
719 | AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn); |
720 | ||
914dc012 GS |
721 | // TODO: Actually handle errors |
722 | if (error) | |
723 | { | |
724 | delete conn; | |
725 | } | |
726 | ||
727 | // Restrict callers by IP. It is important to | |
728 | // do this before starting client thread, to filter out | |
729 | // certain DoS and misbehaving clients. | |
a0780ba0 GS |
730 | else if (tcp_conn |
731 | && !ClientAllowed(tcp_conn->peer.address())) | |
914dc012 GS |
732 | { |
733 | // Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake. | |
734 | if (!fUseSSL) | |
285746d3 | 735 | conn->stream() << HTTPReply(HTTP_FORBIDDEN, "", false) << std::flush; |
914dc012 GS |
736 | delete conn; |
737 | } | |
738 | ||
739 | // start HTTP client thread | |
4d1d94c5 | 740 | else if (!NewThread(ThreadRPCServer3, conn)) { |
914dc012 GS |
741 | printf("Failed to create RPC server client thread\n"); |
742 | delete conn; | |
743 | } | |
744 | ||
745 | vnThreadsRunning[THREAD_RPCLISTENER]--; | |
746 | } | |
747 | ||
69d605f4 WL |
748 | void ThreadRPCServer2(void* parg) |
749 | { | |
750 | printf("ThreadRPCServer started\n"); | |
751 | ||
f81ce5bd | 752 | strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; |
7e1610d5 JG |
753 | if ((mapArgs["-rpcpassword"] == "") || |
754 | (mapArgs["-rpcuser"] == mapArgs["-rpcpassword"])) | |
69d605f4 | 755 | { |
b04f301c GM |
756 | unsigned char rand_pwd[32]; |
757 | RAND_bytes(rand_pwd, 32); | |
69d605f4 WL |
758 | string strWhatAmI = "To use bitcoind"; |
759 | if (mapArgs.count("-server")) | |
760 | strWhatAmI = strprintf(_("To use the %s option"), "\"-server\""); | |
761 | else if (mapArgs.count("-daemon")) | |
762 | strWhatAmI = strprintf(_("To use the %s option"), "\"-daemon\""); | |
ab1b288f | 763 | uiInterface.ThreadSafeMessageBox(strprintf( |
5a60b66a WL |
764 | _("%s, you must set a rpcpassword in the configuration file:\n %s\n" |
765 | "It is recommended you use the following random password:\n" | |
766 | "rpcuser=bitcoinrpc\n" | |
767 | "rpcpassword=%s\n" | |
768 | "(you do not need to remember this password)\n" | |
7e1610d5 | 769 | "The username and password MUST NOT be the same.\n" |
5a60b66a | 770 | "If the file does not exist, create it with owner-readable-only file permissions.\n"), |
69d605f4 | 771 | strWhatAmI.c_str(), |
ee12c3d6 | 772 | GetConfigFile().string().c_str(), |
5a60b66a | 773 | EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str()), |
5350ea41 | 774 | "", CClientUIInterface::MSG_ERROR); |
9247134e | 775 | StartShutdown(); |
69d605f4 WL |
776 | return; |
777 | } | |
778 | ||
914dc012 | 779 | const bool fUseSSL = GetBoolArg("-rpcssl"); |
69d605f4 WL |
780 | |
781 | asio::io_service io_service; | |
fbf9df2e | 782 | |
69d605f4 WL |
783 | ssl::context context(io_service, ssl::context::sslv23); |
784 | if (fUseSSL) | |
785 | { | |
786 | context.set_options(ssl::context::no_sslv2); | |
93fb7489 PK |
787 | |
788 | filesystem::path pathCertFile(GetArg("-rpcsslcertificatechainfile", "server.cert")); | |
789 | if (!pathCertFile.is_complete()) pathCertFile = filesystem::path(GetDataDir()) / pathCertFile; | |
ee12c3d6 | 790 | if (filesystem::exists(pathCertFile)) context.use_certificate_chain_file(pathCertFile.string()); |
93fb7489 PK |
791 | else printf("ThreadRPCServer ERROR: missing server certificate file %s\n", pathCertFile.string().c_str()); |
792 | ||
793 | filesystem::path pathPKFile(GetArg("-rpcsslprivatekeyfile", "server.pem")); | |
794 | if (!pathPKFile.is_complete()) pathPKFile = filesystem::path(GetDataDir()) / pathPKFile; | |
ee12c3d6 | 795 | if (filesystem::exists(pathPKFile)) context.use_private_key_file(pathPKFile.string(), ssl::context::pem); |
93fb7489 PK |
796 | else printf("ThreadRPCServer ERROR: missing server private key file %s\n", pathPKFile.string().c_str()); |
797 | ||
798 | string strCiphers = GetArg("-rpcsslciphers", "TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH"); | |
799 | SSL_CTX_set_cipher_list(context.impl(), strCiphers.c_str()); | |
69d605f4 | 800 | } |
69d605f4 | 801 | |
c1ecab81 GS |
802 | // Try a dual IPv6/IPv4 socket, falling back to separate IPv4 and IPv6 sockets |
803 | const bool loopback = !mapArgs.count("-rpcallowip"); | |
804 | asio::ip::address bindAddress = loopback ? asio::ip::address_v6::loopback() : asio::ip::address_v6::any(); | |
b202d430 | 805 | ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", GetDefaultRPCPort())); |
c1d79812 PW |
806 | boost::system::error_code v6_only_error; |
807 | boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(io_service)); | |
c1ecab81 | 808 | |
7cf3d2cc MC |
809 | boost::signals2::signal<void ()> StopRequests; |
810 | ||
c1d79812 PW |
811 | bool fListening = false; |
812 | std::string strerr; | |
c1ecab81 GS |
813 | try |
814 | { | |
ad25804f GS |
815 | acceptor->open(endpoint.protocol()); |
816 | acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); | |
c1ecab81 GS |
817 | |
818 | // Try making the socket dual IPv6/IPv4 (if listening on the "any" address) | |
ad25804f | 819 | acceptor->set_option(boost::asio::ip::v6_only(loopback), v6_only_error); |
c1ecab81 | 820 | |
ad25804f GS |
821 | acceptor->bind(endpoint); |
822 | acceptor->listen(socket_base::max_connections); | |
c1ecab81 | 823 | |
ad25804f GS |
824 | RPCListen(acceptor, context, fUseSSL); |
825 | // Cancel outstanding listen-requests for this acceptor when shutting down | |
7cf3d2cc | 826 | StopRequests.connect(signals2::slot<void ()>( |
ad25804f GS |
827 | static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get()) |
828 | .track(acceptor)); | |
c1ecab81 | 829 | |
c1d79812 PW |
830 | fListening = true; |
831 | } | |
832 | catch(boost::system::system_error &e) | |
833 | { | |
95e625d2 | 834 | strerr = strprintf(_("An error occurred while setting up the RPC port %u for listening on IPv6, falling back to IPv4: %s"), endpoint.port(), e.what()); |
c1d79812 PW |
835 | } |
836 | ||
837 | try { | |
c1ecab81 | 838 | // If dual IPv6/IPv4 failed (or we're opening loopback interfaces only), open IPv4 separately |
c1d79812 | 839 | if (!fListening || loopback || v6_only_error) |
c1ecab81 GS |
840 | { |
841 | bindAddress = loopback ? asio::ip::address_v4::loopback() : asio::ip::address_v4::any(); | |
842 | endpoint.address(bindAddress); | |
843 | ||
ad25804f GS |
844 | acceptor.reset(new ip::tcp::acceptor(io_service)); |
845 | acceptor->open(endpoint.protocol()); | |
846 | acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); | |
847 | acceptor->bind(endpoint); | |
848 | acceptor->listen(socket_base::max_connections); | |
849 | ||
850 | RPCListen(acceptor, context, fUseSSL); | |
851 | // Cancel outstanding listen-requests for this acceptor when shutting down | |
7cf3d2cc | 852 | StopRequests.connect(signals2::slot<void ()>( |
ad25804f GS |
853 | static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get()) |
854 | .track(acceptor)); | |
c1d79812 PW |
855 | |
856 | fListening = true; | |
c1ecab81 GS |
857 | } |
858 | } | |
859 | catch(boost::system::system_error &e) | |
860 | { | |
463a1cab | 861 | strerr = strprintf(_("An error occurred while setting up the RPC port %u for listening on IPv4: %s"), endpoint.port(), e.what()); |
c1d79812 PW |
862 | } |
863 | ||
864 | if (!fListening) { | |
5350ea41 | 865 | uiInterface.ThreadSafeMessageBox(strerr, "", CClientUIInterface::MSG_ERROR); |
07368a9e | 866 | StartShutdown(); |
c1ecab81 GS |
867 | return; |
868 | } | |
69d605f4 | 869 | |
914dc012 | 870 | vnThreadsRunning[THREAD_RPCLISTENER]--; |
18c4beb0 MC |
871 | while (!fShutdown) |
872 | io_service.run_one(); | |
914dc012 | 873 | vnThreadsRunning[THREAD_RPCLISTENER]++; |
7cf3d2cc | 874 | StopRequests(); |
e9205293 DJS |
875 | } |
876 | ||
c6494d82 JG |
877 | class JSONRequest |
878 | { | |
879 | public: | |
880 | Value id; | |
881 | string strMethod; | |
882 | Array params; | |
883 | ||
884 | JSONRequest() { id = Value::null; } | |
885 | void parse(const Value& valRequest); | |
886 | }; | |
887 | ||
888 | void JSONRequest::parse(const Value& valRequest) | |
889 | { | |
890 | // Parse request | |
891 | if (valRequest.type() != obj_type) | |
738835d7 | 892 | throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object"); |
c6494d82 JG |
893 | const Object& request = valRequest.get_obj(); |
894 | ||
895 | // Parse id now so errors from here on will have the id | |
896 | id = find_value(request, "id"); | |
897 | ||
898 | // Parse method | |
899 | Value valMethod = find_value(request, "method"); | |
900 | if (valMethod.type() == null_type) | |
738835d7 | 901 | throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method"); |
c6494d82 | 902 | if (valMethod.type() != str_type) |
738835d7 | 903 | throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); |
c6494d82 | 904 | strMethod = valMethod.get_str(); |
44427fa8 | 905 | if (strMethod != "getwork" && strMethod != "getblocktemplate") |
c6494d82 JG |
906 | printf("ThreadRPCServer method=%s\n", strMethod.c_str()); |
907 | ||
908 | // Parse params | |
909 | Value valParams = find_value(request, "params"); | |
910 | if (valParams.type() == array_type) | |
911 | params = valParams.get_array(); | |
912 | else if (valParams.type() == null_type) | |
913 | params = Array(); | |
914 | else | |
738835d7 | 915 | throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array"); |
c6494d82 JG |
916 | } |
917 | ||
61338901 JG |
918 | static Object JSONRPCExecOne(const Value& req) |
919 | { | |
920 | Object rpc_result; | |
921 | ||
922 | JSONRequest jreq; | |
923 | try { | |
924 | jreq.parse(req); | |
925 | ||
926 | Value result = tableRPC.execute(jreq.strMethod, jreq.params); | |
927 | rpc_result = JSONRPCReplyObj(result, Value::null, jreq.id); | |
928 | } | |
929 | catch (Object& objError) | |
930 | { | |
931 | rpc_result = JSONRPCReplyObj(Value::null, objError, jreq.id); | |
932 | } | |
933 | catch (std::exception& e) | |
934 | { | |
935 | rpc_result = JSONRPCReplyObj(Value::null, | |
738835d7 | 936 | JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); |
61338901 JG |
937 | } |
938 | ||
939 | return rpc_result; | |
940 | } | |
941 | ||
942 | static string JSONRPCExecBatch(const Array& vReq) | |
943 | { | |
944 | Array ret; | |
945 | for (unsigned int reqIdx = 0; reqIdx < vReq.size(); reqIdx++) | |
946 | ret.push_back(JSONRPCExecOne(vReq[reqIdx])); | |
947 | ||
948 | return write_string(Value(ret), false) + "\n"; | |
949 | } | |
950 | ||
4e97a9d9 MC |
951 | static CCriticalSection cs_THREAD_RPCHANDLER; |
952 | ||
e9205293 DJS |
953 | void ThreadRPCServer3(void* parg) |
954 | { | |
96931d6f GS |
955 | // Make this thread recognisable as the RPC handler |
956 | RenameThread("bitcoin-rpchand"); | |
957 | ||
4e97a9d9 MC |
958 | { |
959 | LOCK(cs_THREAD_RPCHANDLER); | |
960 | vnThreadsRunning[THREAD_RPCHANDLER]++; | |
961 | } | |
e9205293 | 962 | AcceptedConnection *conn = (AcceptedConnection *) parg; |
69d605f4 | 963 | |
96c52695 DJS |
964 | bool fRun = true; |
965 | loop { | |
966 | if (fShutdown || !fRun) | |
967 | { | |
a0780ba0 | 968 | conn->close(); |
96c52695 | 969 | delete conn; |
4e97a9d9 MC |
970 | { |
971 | LOCK(cs_THREAD_RPCHANDLER); | |
972 | --vnThreadsRunning[THREAD_RPCHANDLER]; | |
973 | } | |
96c52695 DJS |
974 | return; |
975 | } | |
69d605f4 | 976 | |
2306dc4b | 977 | int nProto = 0; |
69d605f4 | 978 | map<string, string> mapHeaders; |
fcf234fc JG |
979 | string strRequest, strMethod, strURI; |
980 | ||
981 | // Read HTTP request line | |
982 | if (!ReadHTTPRequestLine(conn->stream(), nProto, strMethod, strURI)) | |
983 | break; | |
69d605f4 | 984 | |
2306dc4b JG |
985 | // Read HTTP message headers and body |
986 | ReadHTTPMessage(conn->stream(), mapHeaders, strRequest, nProto); | |
69d605f4 WL |
987 | |
988 | // Check authorization | |
989 | if (mapHeaders.count("authorization") == 0) | |
990 | { | |
285746d3 | 991 | conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush; |
e9205293 | 992 | break; |
69d605f4 WL |
993 | } |
994 | if (!HTTPAuthorized(mapHeaders)) | |
995 | { | |
a0780ba0 | 996 | printf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string().c_str()); |
b04f301c GM |
997 | /* Deter brute-forcing short passwords. |
998 | If this results in a DOS the user really | |
999 | shouldn't have their RPC port exposed.*/ | |
1000 | if (mapArgs["-rpcpassword"].size() < 20) | |
1001 | Sleep(250); | |
69d605f4 | 1002 | |
285746d3 | 1003 | conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush; |
e9205293 | 1004 | break; |
69d605f4 | 1005 | } |
96c52695 DJS |
1006 | if (mapHeaders["connection"] == "close") |
1007 | fRun = false; | |
69d605f4 | 1008 | |
c6494d82 | 1009 | JSONRequest jreq; |
69d605f4 WL |
1010 | try |
1011 | { | |
1012 | // Parse request | |
1013 | Value valRequest; | |
61338901 | 1014 | if (!read_string(strRequest, valRequest)) |
738835d7 | 1015 | throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); |
69d605f4 | 1016 | |
61338901 JG |
1017 | string strReply; |
1018 | ||
1019 | // singleton request | |
1020 | if (valRequest.type() == obj_type) { | |
1021 | jreq.parse(valRequest); | |
c6494d82 | 1022 | |
61338901 | 1023 | Value result = tableRPC.execute(jreq.strMethod, jreq.params); |
460c51fd | 1024 | |
61338901 JG |
1025 | // Send reply |
1026 | strReply = JSONRPCReply(result, Value::null, jreq.id); | |
1027 | ||
1028 | // array of requests | |
1029 | } else if (valRequest.type() == array_type) | |
1030 | strReply = JSONRPCExecBatch(valRequest.get_array()); | |
1031 | else | |
738835d7 | 1032 | throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); |
ea0796bd | 1033 | |
285746d3 | 1034 | conn->stream() << HTTPReply(HTTP_OK, strReply, fRun) << std::flush; |
69d605f4 WL |
1035 | } |
1036 | catch (Object& objError) | |
1037 | { | |
c6494d82 | 1038 | ErrorReply(conn->stream(), objError, jreq.id); |
e9205293 | 1039 | break; |
69d605f4 WL |
1040 | } |
1041 | catch (std::exception& e) | |
1042 | { | |
738835d7 | 1043 | ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); |
e9205293 | 1044 | break; |
69d605f4 WL |
1045 | } |
1046 | } | |
96c52695 | 1047 | |
e9205293 | 1048 | delete conn; |
4e97a9d9 MC |
1049 | { |
1050 | LOCK(cs_THREAD_RPCHANDLER); | |
1051 | vnThreadsRunning[THREAD_RPCHANDLER]--; | |
1052 | } | |
69d605f4 WL |
1053 | } |
1054 | ||
460c51fd WL |
1055 | json_spirit::Value CRPCTable::execute(const std::string &strMethod, const json_spirit::Array ¶ms) const |
1056 | { | |
1057 | // Find method | |
1058 | const CRPCCommand *pcmd = tableRPC[strMethod]; | |
1059 | if (!pcmd) | |
738835d7 | 1060 | throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); |
69d605f4 | 1061 | |
460c51fd WL |
1062 | // Observe safe mode |
1063 | string strWarning = GetWarnings("rpc"); | |
1064 | if (strWarning != "" && !GetBoolArg("-disablesafemode") && | |
1065 | !pcmd->okSafeMode) | |
738835d7 | 1066 | throw JSONRPCError(RPC_FORBIDDEN_BY_SAFE_MODE, string("Safe mode: ") + strWarning); |
460c51fd WL |
1067 | |
1068 | try | |
1069 | { | |
1070 | // Execute | |
1071 | Value result; | |
1072 | { | |
0e1d3551 JG |
1073 | if (pcmd->unlocked) |
1074 | result = pcmd->actor(params, false); | |
1075 | else { | |
1076 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
1077 | result = pcmd->actor(params, false); | |
1078 | } | |
460c51fd WL |
1079 | } |
1080 | return result; | |
1081 | } | |
1082 | catch (std::exception& e) | |
1083 | { | |
738835d7 | 1084 | throw JSONRPCError(RPC_MISC_ERROR, e.what()); |
460c51fd WL |
1085 | } |
1086 | } | |
69d605f4 WL |
1087 | |
1088 | ||
1089 | Object CallRPC(const string& strMethod, const Array& params) | |
1090 | { | |
1091 | if (mapArgs["-rpcuser"] == "" && mapArgs["-rpcpassword"] == "") | |
1092 | throw runtime_error(strprintf( | |
1093 | _("You must set rpcpassword=<password> in the configuration file:\n%s\n" | |
1094 | "If the file does not exist, create it with owner-readable-only file permissions."), | |
ee12c3d6 | 1095 | GetConfigFile().string().c_str())); |
69d605f4 WL |
1096 | |
1097 | // Connect to localhost | |
1098 | bool fUseSSL = GetBoolArg("-rpcssl"); | |
69d605f4 WL |
1099 | asio::io_service io_service; |
1100 | ssl::context context(io_service, ssl::context::sslv23); | |
1101 | context.set_options(ssl::context::no_sslv2); | |
a0780ba0 GS |
1102 | asio::ssl::stream<asio::ip::tcp::socket> sslStream(io_service, context); |
1103 | SSLIOStreamDevice<asio::ip::tcp> d(sslStream, fUseSSL); | |
1104 | iostreams::stream< SSLIOStreamDevice<asio::ip::tcp> > stream(d); | |
b202d430 | 1105 | if (!d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(GetDefaultRPCPort())))) |
69d605f4 | 1106 | throw runtime_error("couldn't connect to server"); |
69d605f4 WL |
1107 | |
1108 | // HTTP basic authentication | |
1109 | string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]); | |
1110 | map<string, string> mapRequestHeaders; | |
1111 | mapRequestHeaders["Authorization"] = string("Basic ") + strUserPass64; | |
1112 | ||
1113 | // Send request | |
1114 | string strRequest = JSONRPCRequest(strMethod, params, 1); | |
1115 | string strPost = HTTPPost(strRequest, mapRequestHeaders); | |
1116 | stream << strPost << std::flush; | |
1117 | ||
2306dc4b JG |
1118 | // Receive HTTP reply status |
1119 | int nProto = 0; | |
1120 | int nStatus = ReadHTTPStatus(stream, nProto); | |
1121 | ||
1122 | // Receive HTTP reply message headers and body | |
69d605f4 WL |
1123 | map<string, string> mapHeaders; |
1124 | string strReply; | |
2306dc4b JG |
1125 | ReadHTTPMessage(stream, mapHeaders, strReply, nProto); |
1126 | ||
285746d3 | 1127 | if (nStatus == HTTP_UNAUTHORIZED) |
69d605f4 | 1128 | throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); |
285746d3 | 1129 | else if (nStatus >= 400 && nStatus != HTTP_BAD_REQUEST && nStatus != HTTP_NOT_FOUND && nStatus != HTTP_INTERNAL_SERVER_ERROR) |
69d605f4 WL |
1130 | throw runtime_error(strprintf("server returned HTTP error %d", nStatus)); |
1131 | else if (strReply.empty()) | |
1132 | throw runtime_error("no response from server"); | |
1133 | ||
1134 | // Parse reply | |
1135 | Value valReply; | |
1136 | if (!read_string(strReply, valReply)) | |
1137 | throw runtime_error("couldn't parse reply from server"); | |
1138 | const Object& reply = valReply.get_obj(); | |
1139 | if (reply.empty()) | |
1140 | throw runtime_error("expected reply to have result, error and id properties"); | |
1141 | ||
1142 | return reply; | |
1143 | } | |
1144 | ||
1145 | ||
1146 | ||
1147 | ||
1148 | template<typename T> | |
cc6dfd1f | 1149 | void ConvertTo(Value& value, bool fAllowNull=false) |
69d605f4 | 1150 | { |
cc6dfd1f GA |
1151 | if (fAllowNull && value.type() == null_type) |
1152 | return; | |
69d605f4 WL |
1153 | if (value.type() == str_type) |
1154 | { | |
1155 | // reinterpret string as unquoted json value | |
1156 | Value value2; | |
18871d47 GA |
1157 | string strJSON = value.get_str(); |
1158 | if (!read_string(strJSON, value2)) | |
1159 | throw runtime_error(string("Error parsing JSON:")+strJSON); | |
cc6dfd1f GA |
1160 | ConvertTo<T>(value2, fAllowNull); |
1161 | value = value2; | |
69d605f4 WL |
1162 | } |
1163 | else | |
1164 | { | |
1165 | value = value.get_value<T>(); | |
1166 | } | |
1167 | } | |
1168 | ||
460c51fd WL |
1169 | // Convert strings to command-specific RPC representation |
1170 | Array RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams) | |
1171 | { | |
1172 | Array params; | |
1173 | BOOST_FOREACH(const std::string ¶m, strParams) | |
1174 | params.push_back(param); | |
1175 | ||
1176 | int n = params.size(); | |
1177 | ||
1178 | // | |
1179 | // Special case non-string parameter types | |
1180 | // | |
3731f578 | 1181 | if (strMethod == "stop" && n > 0) ConvertTo<bool>(params[0]); |
460c51fd WL |
1182 | if (strMethod == "setgenerate" && n > 0) ConvertTo<bool>(params[0]); |
1183 | if (strMethod == "setgenerate" && n > 1) ConvertTo<boost::int64_t>(params[1]); | |
1184 | if (strMethod == "sendtoaddress" && n > 1) ConvertTo<double>(params[1]); | |
1185 | if (strMethod == "settxfee" && n > 0) ConvertTo<double>(params[0]); | |
1186 | if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo<boost::int64_t>(params[1]); | |
1187 | if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo<boost::int64_t>(params[1]); | |
1188 | if (strMethod == "listreceivedbyaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); | |
1189 | if (strMethod == "listreceivedbyaddress" && n > 1) ConvertTo<bool>(params[1]); | |
1190 | if (strMethod == "listreceivedbyaccount" && n > 0) ConvertTo<boost::int64_t>(params[0]); | |
1191 | if (strMethod == "listreceivedbyaccount" && n > 1) ConvertTo<bool>(params[1]); | |
1192 | if (strMethod == "getbalance" && n > 1) ConvertTo<boost::int64_t>(params[1]); | |
1193 | if (strMethod == "getblockhash" && n > 0) ConvertTo<boost::int64_t>(params[0]); | |
1194 | if (strMethod == "move" && n > 2) ConvertTo<double>(params[2]); | |
1195 | if (strMethod == "move" && n > 3) ConvertTo<boost::int64_t>(params[3]); | |
1196 | if (strMethod == "sendfrom" && n > 2) ConvertTo<double>(params[2]); | |
1197 | if (strMethod == "sendfrom" && n > 3) ConvertTo<boost::int64_t>(params[3]); | |
1198 | if (strMethod == "listtransactions" && n > 1) ConvertTo<boost::int64_t>(params[1]); | |
1199 | if (strMethod == "listtransactions" && n > 2) ConvertTo<boost::int64_t>(params[2]); | |
1200 | if (strMethod == "listaccounts" && n > 0) ConvertTo<boost::int64_t>(params[0]); | |
1201 | if (strMethod == "walletpassphrase" && n > 1) ConvertTo<boost::int64_t>(params[1]); | |
44427fa8 | 1202 | if (strMethod == "getblocktemplate" && n > 0) ConvertTo<Object>(params[0]); |
460c51fd | 1203 | if (strMethod == "listsinceblock" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
98474d3d GA |
1204 | if (strMethod == "sendmany" && n > 1) ConvertTo<Object>(params[1]); |
1205 | if (strMethod == "sendmany" && n > 2) ConvertTo<boost::int64_t>(params[2]); | |
1206 | if (strMethod == "addmultisigaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); | |
1207 | if (strMethod == "addmultisigaddress" && n > 1) ConvertTo<Array>(params[1]); | |
34226be7 GA |
1208 | if (strMethod == "createmultisig" && n > 0) ConvertTo<boost::int64_t>(params[0]); |
1209 | if (strMethod == "createmultisig" && n > 1) ConvertTo<Array>(params[1]); | |
a2709fad GA |
1210 | if (strMethod == "listunspent" && n > 0) ConvertTo<boost::int64_t>(params[0]); |
1211 | if (strMethod == "listunspent" && n > 1) ConvertTo<boost::int64_t>(params[1]); | |
92735bca | 1212 | if (strMethod == "listunspent" && n > 2) ConvertTo<Array>(params[2]); |
a2709fad GA |
1213 | if (strMethod == "getrawtransaction" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
1214 | if (strMethod == "createrawtransaction" && n > 0) ConvertTo<Array>(params[0]); | |
1215 | if (strMethod == "createrawtransaction" && n > 1) ConvertTo<Object>(params[1]); | |
cc6dfd1f GA |
1216 | if (strMethod == "signrawtransaction" && n > 1) ConvertTo<Array>(params[1], true); |
1217 | if (strMethod == "signrawtransaction" && n > 2) ConvertTo<Array>(params[2], true); | |
beeb5761 PW |
1218 | if (strMethod == "gettxout" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
1219 | if (strMethod == "gettxout" && n > 2) ConvertTo<bool>(params[2]); | |
fdbb537d JG |
1220 | if (strMethod == "lockunspent" && n > 0) ConvertTo<bool>(params[0]); |
1221 | if (strMethod == "lockunspent" && n > 1) ConvertTo<Array>(params[1]); | |
5dfbd4ff | 1222 | if (strMethod == "importprivkey" && n > 2) ConvertTo<bool>(params[2]); |
98474d3d | 1223 | |
460c51fd WL |
1224 | return params; |
1225 | } | |
1226 | ||
69d605f4 WL |
1227 | int CommandLineRPC(int argc, char *argv[]) |
1228 | { | |
1229 | string strPrint; | |
1230 | int nRet = 0; | |
1231 | try | |
1232 | { | |
1233 | // Skip switches | |
1234 | while (argc > 1 && IsSwitchChar(argv[1][0])) | |
1235 | { | |
1236 | argc--; | |
1237 | argv++; | |
1238 | } | |
1239 | ||
1240 | // Method | |
1241 | if (argc < 2) | |
1242 | throw runtime_error("too few parameters"); | |
1243 | string strMethod = argv[1]; | |
1244 | ||
1245 | // Parameters default to strings | |
460c51fd WL |
1246 | std::vector<std::string> strParams(&argv[2], &argv[argc]); |
1247 | Array params = RPCConvertValues(strMethod, strParams); | |
69d605f4 WL |
1248 | |
1249 | // Execute | |
1250 | Object reply = CallRPC(strMethod, params); | |
1251 | ||
1252 | // Parse reply | |
1253 | const Value& result = find_value(reply, "result"); | |
1254 | const Value& error = find_value(reply, "error"); | |
69d605f4 WL |
1255 | |
1256 | if (error.type() != null_type) | |
1257 | { | |
1258 | // Error | |
1259 | strPrint = "error: " + write_string(error, false); | |
1260 | int code = find_value(error.get_obj(), "code").get_int(); | |
1261 | nRet = abs(code); | |
1262 | } | |
1263 | else | |
1264 | { | |
1265 | // Result | |
1266 | if (result.type() == null_type) | |
1267 | strPrint = ""; | |
1268 | else if (result.type() == str_type) | |
1269 | strPrint = result.get_str(); | |
1270 | else | |
1271 | strPrint = write_string(result, true); | |
1272 | } | |
1273 | } | |
1274 | catch (std::exception& e) | |
1275 | { | |
1276 | strPrint = string("error: ") + e.what(); | |
1277 | nRet = 87; | |
1278 | } | |
1279 | catch (...) | |
1280 | { | |
1281 | PrintException(NULL, "CommandLineRPC()"); | |
1282 | } | |
1283 | ||
1284 | if (strPrint != "") | |
1285 | { | |
69d605f4 | 1286 | fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); |
69d605f4 WL |
1287 | } |
1288 | return nRet; | |
1289 | } | |
1290 | ||
1291 | ||
1292 | ||
1293 | ||
1294 | #ifdef TEST | |
1295 | int main(int argc, char *argv[]) | |
1296 | { | |
1297 | #ifdef _MSC_VER | |
814efd6f | 1298 | // Turn off Microsoft heap dump noise |
69d605f4 WL |
1299 | _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); |
1300 | _CrtSetReportFile(_CRT_WARN, CreateFile("NUL", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0)); | |
1301 | #endif | |
1302 | setbuf(stdin, NULL); | |
1303 | setbuf(stdout, NULL); | |
1304 | setbuf(stderr, NULL); | |
1305 | ||
1306 | try | |
1307 | { | |
1308 | if (argc >= 2 && string(argv[1]) == "-server") | |
1309 | { | |
1310 | printf("server ready\n"); | |
1311 | ThreadRPCServer(NULL); | |
1312 | } | |
1313 | else | |
1314 | { | |
1315 | return CommandLineRPC(argc, argv); | |
1316 | } | |
1317 | } | |
1318 | catch (std::exception& e) { | |
1319 | PrintException(&e, "main()"); | |
1320 | } catch (...) { | |
1321 | PrintException(NULL, "main()"); | |
1322 | } | |
1323 | return 0; | |
1324 | } | |
1325 | #endif | |
e46704dd PW |
1326 | |
1327 | const CRPCTable tableRPC; |