]>
Commit | Line | Data |
---|---|---|
1 | // Copyright (c) 2010 Satoshi Nakamoto | |
2 | // Copyright (c) 2009-2012 The Bitcoin developers | |
3 | // Distributed under the MIT/X11 software license, see the accompanying | |
4 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | |
5 | ||
6 | #include "init.h" | |
7 | #include "util.h" | |
8 | #include "sync.h" | |
9 | #include "ui_interface.h" | |
10 | #include "base58.h" | |
11 | #include "bitcoinrpc.h" | |
12 | #include "db.h" | |
13 | ||
14 | #include <boost/asio.hpp> | |
15 | #include <boost/asio/ip/v6_only.hpp> | |
16 | #include <boost/bind.hpp> | |
17 | #include <boost/filesystem.hpp> | |
18 | #include <boost/foreach.hpp> | |
19 | #include <boost/iostreams/concepts.hpp> | |
20 | #include <boost/iostreams/stream.hpp> | |
21 | #include <boost/algorithm/string.hpp> | |
22 | #include <boost/lexical_cast.hpp> | |
23 | #include <boost/asio/ssl.hpp> | |
24 | #include <boost/filesystem/fstream.hpp> | |
25 | #include <boost/shared_ptr.hpp> | |
26 | #include <list> | |
27 | ||
28 | using namespace std; | |
29 | using namespace boost; | |
30 | using namespace boost::asio; | |
31 | using namespace json_spirit; | |
32 | ||
33 | void ThreadRPCServer2(void* parg); | |
34 | ||
35 | static std::string strRPCUserColonPass; | |
36 | ||
37 | const Object emptyobj; | |
38 | ||
39 | void ThreadRPCServer3(void* parg); | |
40 | ||
41 | static inline unsigned short GetDefaultRPCPort() | |
42 | { | |
43 | return GetBoolArg("-testnet", false) ? 18332 : 8332; | |
44 | } | |
45 | ||
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 | ||
54 | void RPCTypeCheck(const Array& params, | |
55 | const list<Value_type>& typesExpected, | |
56 | bool fAllowNull) | |
57 | { | |
58 | unsigned int i = 0; | |
59 | BOOST_FOREACH(Value_type t, typesExpected) | |
60 | { | |
61 | if (params.size() <= i) | |
62 | break; | |
63 | ||
64 | const Value& v = params[i]; | |
65 | if (!((v.type() == t) || (fAllowNull && (v.type() == null_type)))) | |
66 | { | |
67 | string err = strprintf("Expected type %s, got %s", | |
68 | Value_type_name[t], Value_type_name[v.type()]); | |
69 | throw JSONRPCError(RPC_TYPE_ERROR, err); | |
70 | } | |
71 | i++; | |
72 | } | |
73 | } | |
74 | ||
75 | void RPCTypeCheck(const Object& o, | |
76 | const map<string, Value_type>& typesExpected, | |
77 | bool fAllowNull) | |
78 | { | |
79 | BOOST_FOREACH(const PAIRTYPE(string, Value_type)& t, typesExpected) | |
80 | { | |
81 | const Value& v = find_value(o, t.first); | |
82 | if (!fAllowNull && v.type() == null_type) | |
83 | throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing %s", t.first.c_str())); | |
84 | ||
85 | if (!((v.type() == t.second) || (fAllowNull && (v.type() == null_type)))) | |
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()]); | |
89 | throw JSONRPCError(RPC_TYPE_ERROR, err); | |
90 | } | |
91 | } | |
92 | } | |
93 | ||
94 | int64 AmountFromValue(const Value& value) | |
95 | { | |
96 | double dAmount = value.get_real(); | |
97 | if (dAmount <= 0.0 || dAmount > 21000000.0) | |
98 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); | |
99 | int64 nAmount = roundint64(dAmount * COIN); | |
100 | if (!MoneyRange(nAmount)) | |
101 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); | |
102 | return nAmount; | |
103 | } | |
104 | ||
105 | Value ValueFromAmount(int64 amount) | |
106 | { | |
107 | return (double)amount / (double)COIN; | |
108 | } | |
109 | ||
110 | std::string HexBits(unsigned int nBits) | |
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 | ||
120 | ||
121 | ||
122 | /// | |
123 | /// Note: This interface may still be subject to change. | |
124 | /// | |
125 | ||
126 | string CRPCTable::help(string strCommand) const | |
127 | { | |
128 | string strRet; | |
129 | set<rpcfn_type> setDone; | |
130 | for (map<string, const CRPCCommand*>::const_iterator mi = mapCommands.begin(); mi != mapCommands.end(); ++mi) | |
131 | { | |
132 | const CRPCCommand *pcmd = mi->second; | |
133 | string strMethod = mi->first; | |
134 | // We already filter duplicates, but these deprecated screw up the sort order | |
135 | if (strMethod.find("label") != string::npos) | |
136 | continue; | |
137 | if (strCommand != "" && strMethod != strCommand) | |
138 | continue; | |
139 | try | |
140 | { | |
141 | Array params; | |
142 | rpcfn_type pfn = pcmd->actor; | |
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 == "") | |
151 | if (strHelp.find('\n') != string::npos) | |
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 | ||
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 | ||
176 | ||
177 | Value stop(const Array& params, bool fHelp) | |
178 | { | |
179 | // Accept the deprecated and ignored 'detach´ boolean argument | |
180 | if (fHelp || params.size() > 1) | |
181 | throw runtime_error( | |
182 | "stop\n" | |
183 | "Stop Bitcoin server."); | |
184 | // Shutdown will take long enough that the response should get back | |
185 | StartShutdown(); | |
186 | return "Bitcoin server stopping"; | |
187 | } | |
188 | ||
189 | ||
190 | ||
191 | // | |
192 | // Call Table | |
193 | // | |
194 | ||
195 | ||
196 | static const CRPCCommand vRPCCommands[] = | |
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 }, | |
232 | { "createmultisig", &createmultisig, true, true }, | |
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 }, | |
238 | { "listaddressgroupings", &listaddressgroupings, false, false }, | |
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 }, | |
255 | { "gettxoutsetinfo", &gettxoutsetinfo, true, false }, | |
256 | { "gettxout", &gettxout, true, false }, | |
257 | { "lockunspent", &lockunspent, false, false }, | |
258 | { "listlockunspent", &listlockunspent, false, false }, | |
259 | }; | |
260 | ||
261 | CRPCTable::CRPCTable() | |
262 | { | |
263 | unsigned int vcidx; | |
264 | for (vcidx = 0; vcidx < (sizeof(vRPCCommands) / sizeof(vRPCCommands[0])); vcidx++) | |
265 | { | |
266 | const CRPCCommand *pcmd; | |
267 | ||
268 | pcmd = &vRPCCommands[vcidx]; | |
269 | mapCommands[pcmd->name] = pcmd; | |
270 | } | |
271 | } | |
272 | ||
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 | } | |
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" | |
296 | << "Connection: close\r\n" | |
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)); | |
312 | setlocale(LC_TIME, "C"); // we want POSIX (aka "C") weekday/month strings | |
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 | ||
318 | static string HTTPReply(int nStatus, const string& strMsg, bool keepalive) | |
319 | { | |
320 | if (nStatus == HTTP_UNAUTHORIZED) | |
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()); | |
337 | const char *cStatus; | |
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"; | |
343 | else cStatus = ""; | |
344 | return strprintf( | |
345 | "HTTP/1.1 %d %s\r\n" | |
346 | "Date: %s\r\n" | |
347 | "Connection: %s\r\n" | |
348 | "Content-Length: %"PRIszu"\r\n" | |
349 | "Content-Type: application/json\r\n" | |
350 | "Server: bitcoin-json-rpc/%s\r\n" | |
351 | "\r\n" | |
352 | "%s", | |
353 | nStatus, | |
354 | cStatus, | |
355 | rfc1123Time().c_str(), | |
356 | keepalive ? "keep-alive" : "close", | |
357 | strMsg.size(), | |
358 | FormatFullVersion().c_str(), | |
359 | strMsg.c_str()); | |
360 | } | |
361 | ||
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 | ||
397 | int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto) | |
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) | |
404 | return HTTP_INTERNAL_SERVER_ERROR; | |
405 | proto = 0; | |
406 | const char *ver = strstr(str.c_str(), "HTTP/1."); | |
407 | if (ver != NULL) | |
408 | proto = atoi(ver+7); | |
409 | return atoi(vWords[1].c_str()); | |
410 | } | |
411 | ||
412 | int ReadHTTPHeaders(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet) | |
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 | ||
437 | int ReadHTTPMessage(std::basic_istream<char>& stream, map<string, | |
438 | string>& mapHeadersRet, string& strMessageRet, | |
439 | int nProto) | |
440 | { | |
441 | mapHeadersRet.clear(); | |
442 | strMessageRet = ""; | |
443 | ||
444 | // Read header | |
445 | int nLen = ReadHTTPHeaders(stream, mapHeadersRet); | |
446 | if (nLen < 0 || nLen > (int)MAX_SIZE) | |
447 | return HTTP_INTERNAL_SERVER_ERROR; | |
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 | ||
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 | ||
467 | return HTTP_OK; | |
468 | } | |
469 | ||
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); | |
477 | return strUserPass == strRPCUserColonPass; | |
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 | ||
499 | Object JSONRPCReplyObj(const Value& result, const Value& error, const Value& id) | |
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)); | |
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); | |
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 | |
520 | int nStatus = HTTP_INTERNAL_SERVER_ERROR; | |
521 | int code = find_value(objError, "code").get_int(); | |
522 | if (code == RPC_INVALID_REQUEST) nStatus = HTTP_BAD_REQUEST; | |
523 | else if (code == RPC_METHOD_NOT_FOUND) nStatus = HTTP_NOT_FOUND; | |
524 | string strReply = JSONRPCReply(Value::null, objError, id); | |
525 | stream << HTTPReply(nStatus, strReply, false) << std::flush; | |
526 | } | |
527 | ||
528 | bool ClientAllowed(const boost::asio::ip::address& address) | |
529 | { | |
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() | |
537 | || address == asio::ip::address_v6::loopback() | |
538 | || (address.is_v4() | |
539 | // Check whether IPv4 addresses match 127.0.0.0/8 (loopback subnet) | |
540 | && (address.to_v4().to_ulong() & 0xff000000) == 0x7f000000)) | |
541 | return true; | |
542 | ||
543 | const string strAddress = address.to_string(); | |
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 | ||
551 | // | |
552 | // IOStream device that speaks SSL but can also speak non-SSL | |
553 | // | |
554 | template <typename Protocol> | |
555 | class SSLIOStreamDevice : public iostreams::device<iostreams::bidirectional> { | |
556 | public: | |
557 | SSLIOStreamDevice(asio::ssl::stream<typename Protocol::socket> &streamIn, bool fUseSSLIn) : stream(streamIn) | |
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; | |
601 | asio::ssl::stream<typename Protocol::socket>& stream; | |
602 | }; | |
603 | ||
604 | class AcceptedConnection | |
605 | { | |
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 | }; | |
613 | ||
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 | } | |
637 | ||
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; | |
649 | }; | |
650 | ||
651 | void ThreadRPCServer(void* parg) | |
652 | { | |
653 | // Make this thread recognisable as the RPC listener | |
654 | RenameThread("bitcoin-rpclist"); | |
655 | ||
656 | try | |
657 | { | |
658 | vnThreadsRunning[THREAD_RPCLISTENER]++; | |
659 | ThreadRPCServer2(parg); | |
660 | vnThreadsRunning[THREAD_RPCLISTENER]--; | |
661 | } | |
662 | catch (std::exception& e) { | |
663 | vnThreadsRunning[THREAD_RPCLISTENER]--; | |
664 | PrintException(&e, "ThreadRPCServer()"); | |
665 | } catch (...) { | |
666 | vnThreadsRunning[THREAD_RPCLISTENER]--; | |
667 | PrintException(NULL, "ThreadRPCServer()"); | |
668 | } | |
669 | printf("ThreadRPCServer exited\n"); | |
670 | } | |
671 | ||
672 | // Forward declaration required for RPCListen | |
673 | template <typename Protocol, typename SocketAcceptorService> | |
674 | static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, | |
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 | */ | |
683 | template <typename Protocol, typename SocketAcceptorService> | |
684 | static void RPCListen(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, | |
685 | ssl::context& context, | |
686 | const bool fUseSSL) | |
687 | { | |
688 | // Accept connection | |
689 | AcceptedConnectionImpl<Protocol>* conn = new AcceptedConnectionImpl<Protocol>(acceptor->get_io_service(), context, fUseSSL); | |
690 | ||
691 | acceptor->async_accept( | |
692 | conn->sslStream.lowest_layer(), | |
693 | conn->peer, | |
694 | boost::bind(&RPCAcceptHandler<Protocol, SocketAcceptorService>, | |
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 | */ | |
705 | template <typename Protocol, typename SocketAcceptorService> | |
706 | static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, | |
707 | ssl::context& context, | |
708 | const bool fUseSSL, | |
709 | AcceptedConnection* conn, | |
710 | const boost::system::error_code& error) | |
711 | { | |
712 | vnThreadsRunning[THREAD_RPCLISTENER]++; | |
713 | ||
714 | // Immediately start accepting new connections, except when we're cancelled or our socket is closed. | |
715 | if (error != asio::error::operation_aborted | |
716 | && acceptor->is_open()) | |
717 | RPCListen(acceptor, context, fUseSSL); | |
718 | ||
719 | AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn); | |
720 | ||
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. | |
730 | else if (tcp_conn | |
731 | && !ClientAllowed(tcp_conn->peer.address())) | |
732 | { | |
733 | // Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake. | |
734 | if (!fUseSSL) | |
735 | conn->stream() << HTTPReply(HTTP_FORBIDDEN, "", false) << std::flush; | |
736 | delete conn; | |
737 | } | |
738 | ||
739 | // start HTTP client thread | |
740 | else if (!NewThread(ThreadRPCServer3, conn)) { | |
741 | printf("Failed to create RPC server client thread\n"); | |
742 | delete conn; | |
743 | } | |
744 | ||
745 | vnThreadsRunning[THREAD_RPCLISTENER]--; | |
746 | } | |
747 | ||
748 | void ThreadRPCServer2(void* parg) | |
749 | { | |
750 | printf("ThreadRPCServer started\n"); | |
751 | ||
752 | strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; | |
753 | if ((mapArgs["-rpcpassword"] == "") || | |
754 | (mapArgs["-rpcuser"] == mapArgs["-rpcpassword"])) | |
755 | { | |
756 | unsigned char rand_pwd[32]; | |
757 | RAND_bytes(rand_pwd, 32); | |
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\""); | |
763 | uiInterface.ThreadSafeMessageBox(strprintf( | |
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" | |
769 | "The username and password MUST NOT be the same.\n" | |
770 | "If the file does not exist, create it with owner-readable-only file permissions.\n"), | |
771 | strWhatAmI.c_str(), | |
772 | GetConfigFile().string().c_str(), | |
773 | EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str()), | |
774 | "", CClientUIInterface::MSG_ERROR); | |
775 | StartShutdown(); | |
776 | return; | |
777 | } | |
778 | ||
779 | const bool fUseSSL = GetBoolArg("-rpcssl"); | |
780 | ||
781 | asio::io_service io_service; | |
782 | ||
783 | ssl::context context(io_service, ssl::context::sslv23); | |
784 | if (fUseSSL) | |
785 | { | |
786 | context.set_options(ssl::context::no_sslv2); | |
787 | ||
788 | filesystem::path pathCertFile(GetArg("-rpcsslcertificatechainfile", "server.cert")); | |
789 | if (!pathCertFile.is_complete()) pathCertFile = filesystem::path(GetDataDir()) / pathCertFile; | |
790 | if (filesystem::exists(pathCertFile)) context.use_certificate_chain_file(pathCertFile.string()); | |
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; | |
795 | if (filesystem::exists(pathPKFile)) context.use_private_key_file(pathPKFile.string(), ssl::context::pem); | |
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()); | |
800 | } | |
801 | ||
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(); | |
805 | ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", GetDefaultRPCPort())); | |
806 | boost::system::error_code v6_only_error; | |
807 | boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(io_service)); | |
808 | ||
809 | boost::signals2::signal<void ()> StopRequests; | |
810 | ||
811 | bool fListening = false; | |
812 | std::string strerr; | |
813 | try | |
814 | { | |
815 | acceptor->open(endpoint.protocol()); | |
816 | acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); | |
817 | ||
818 | // Try making the socket dual IPv6/IPv4 (if listening on the "any" address) | |
819 | acceptor->set_option(boost::asio::ip::v6_only(loopback), v6_only_error); | |
820 | ||
821 | acceptor->bind(endpoint); | |
822 | acceptor->listen(socket_base::max_connections); | |
823 | ||
824 | RPCListen(acceptor, context, fUseSSL); | |
825 | // Cancel outstanding listen-requests for this acceptor when shutting down | |
826 | StopRequests.connect(signals2::slot<void ()>( | |
827 | static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get()) | |
828 | .track(acceptor)); | |
829 | ||
830 | fListening = true; | |
831 | } | |
832 | catch(boost::system::system_error &e) | |
833 | { | |
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()); | |
835 | } | |
836 | ||
837 | try { | |
838 | // If dual IPv6/IPv4 failed (or we're opening loopback interfaces only), open IPv4 separately | |
839 | if (!fListening || loopback || v6_only_error) | |
840 | { | |
841 | bindAddress = loopback ? asio::ip::address_v4::loopback() : asio::ip::address_v4::any(); | |
842 | endpoint.address(bindAddress); | |
843 | ||
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 | |
852 | StopRequests.connect(signals2::slot<void ()>( | |
853 | static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get()) | |
854 | .track(acceptor)); | |
855 | ||
856 | fListening = true; | |
857 | } | |
858 | } | |
859 | catch(boost::system::system_error &e) | |
860 | { | |
861 | strerr = strprintf(_("An error occurred while setting up the RPC port %u for listening on IPv4: %s"), endpoint.port(), e.what()); | |
862 | } | |
863 | ||
864 | if (!fListening) { | |
865 | uiInterface.ThreadSafeMessageBox(strerr, "", CClientUIInterface::MSG_ERROR); | |
866 | StartShutdown(); | |
867 | return; | |
868 | } | |
869 | ||
870 | vnThreadsRunning[THREAD_RPCLISTENER]--; | |
871 | while (!fShutdown) | |
872 | io_service.run_one(); | |
873 | vnThreadsRunning[THREAD_RPCLISTENER]++; | |
874 | StopRequests(); | |
875 | } | |
876 | ||
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) | |
892 | throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object"); | |
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) | |
901 | throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method"); | |
902 | if (valMethod.type() != str_type) | |
903 | throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); | |
904 | strMethod = valMethod.get_str(); | |
905 | if (strMethod != "getwork" && strMethod != "getblocktemplate") | |
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 | |
915 | throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array"); | |
916 | } | |
917 | ||
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, | |
936 | JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); | |
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 | ||
951 | static CCriticalSection cs_THREAD_RPCHANDLER; | |
952 | ||
953 | void ThreadRPCServer3(void* parg) | |
954 | { | |
955 | // Make this thread recognisable as the RPC handler | |
956 | RenameThread("bitcoin-rpchand"); | |
957 | ||
958 | { | |
959 | LOCK(cs_THREAD_RPCHANDLER); | |
960 | vnThreadsRunning[THREAD_RPCHANDLER]++; | |
961 | } | |
962 | AcceptedConnection *conn = (AcceptedConnection *) parg; | |
963 | ||
964 | bool fRun = true; | |
965 | loop { | |
966 | if (fShutdown || !fRun) | |
967 | { | |
968 | conn->close(); | |
969 | delete conn; | |
970 | { | |
971 | LOCK(cs_THREAD_RPCHANDLER); | |
972 | --vnThreadsRunning[THREAD_RPCHANDLER]; | |
973 | } | |
974 | return; | |
975 | } | |
976 | ||
977 | int nProto = 0; | |
978 | map<string, string> mapHeaders; | |
979 | string strRequest, strMethod, strURI; | |
980 | ||
981 | // Read HTTP request line | |
982 | if (!ReadHTTPRequestLine(conn->stream(), nProto, strMethod, strURI)) | |
983 | break; | |
984 | ||
985 | // Read HTTP message headers and body | |
986 | ReadHTTPMessage(conn->stream(), mapHeaders, strRequest, nProto); | |
987 | ||
988 | // Check authorization | |
989 | if (mapHeaders.count("authorization") == 0) | |
990 | { | |
991 | conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush; | |
992 | break; | |
993 | } | |
994 | if (!HTTPAuthorized(mapHeaders)) | |
995 | { | |
996 | printf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string().c_str()); | |
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); | |
1002 | ||
1003 | conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush; | |
1004 | break; | |
1005 | } | |
1006 | if (mapHeaders["connection"] == "close") | |
1007 | fRun = false; | |
1008 | ||
1009 | JSONRequest jreq; | |
1010 | try | |
1011 | { | |
1012 | // Parse request | |
1013 | Value valRequest; | |
1014 | if (!read_string(strRequest, valRequest)) | |
1015 | throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); | |
1016 | ||
1017 | string strReply; | |
1018 | ||
1019 | // singleton request | |
1020 | if (valRequest.type() == obj_type) { | |
1021 | jreq.parse(valRequest); | |
1022 | ||
1023 | Value result = tableRPC.execute(jreq.strMethod, jreq.params); | |
1024 | ||
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 | |
1032 | throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); | |
1033 | ||
1034 | conn->stream() << HTTPReply(HTTP_OK, strReply, fRun) << std::flush; | |
1035 | } | |
1036 | catch (Object& objError) | |
1037 | { | |
1038 | ErrorReply(conn->stream(), objError, jreq.id); | |
1039 | break; | |
1040 | } | |
1041 | catch (std::exception& e) | |
1042 | { | |
1043 | ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); | |
1044 | break; | |
1045 | } | |
1046 | } | |
1047 | ||
1048 | delete conn; | |
1049 | { | |
1050 | LOCK(cs_THREAD_RPCHANDLER); | |
1051 | vnThreadsRunning[THREAD_RPCHANDLER]--; | |
1052 | } | |
1053 | } | |
1054 | ||
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) | |
1060 | throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); | |
1061 | ||
1062 | // Observe safe mode | |
1063 | string strWarning = GetWarnings("rpc"); | |
1064 | if (strWarning != "" && !GetBoolArg("-disablesafemode") && | |
1065 | !pcmd->okSafeMode) | |
1066 | throw JSONRPCError(RPC_FORBIDDEN_BY_SAFE_MODE, string("Safe mode: ") + strWarning); | |
1067 | ||
1068 | try | |
1069 | { | |
1070 | // Execute | |
1071 | Value result; | |
1072 | { | |
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 | } | |
1079 | } | |
1080 | return result; | |
1081 | } | |
1082 | catch (std::exception& e) | |
1083 | { | |
1084 | throw JSONRPCError(RPC_MISC_ERROR, e.what()); | |
1085 | } | |
1086 | } | |
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."), | |
1095 | GetConfigFile().string().c_str())); | |
1096 | ||
1097 | // Connect to localhost | |
1098 | bool fUseSSL = GetBoolArg("-rpcssl"); | |
1099 | asio::io_service io_service; | |
1100 | ssl::context context(io_service, ssl::context::sslv23); | |
1101 | context.set_options(ssl::context::no_sslv2); | |
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); | |
1105 | if (!d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(GetDefaultRPCPort())))) | |
1106 | throw runtime_error("couldn't connect to server"); | |
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 | ||
1118 | // Receive HTTP reply status | |
1119 | int nProto = 0; | |
1120 | int nStatus = ReadHTTPStatus(stream, nProto); | |
1121 | ||
1122 | // Receive HTTP reply message headers and body | |
1123 | map<string, string> mapHeaders; | |
1124 | string strReply; | |
1125 | ReadHTTPMessage(stream, mapHeaders, strReply, nProto); | |
1126 | ||
1127 | if (nStatus == HTTP_UNAUTHORIZED) | |
1128 | throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); | |
1129 | else if (nStatus >= 400 && nStatus != HTTP_BAD_REQUEST && nStatus != HTTP_NOT_FOUND && nStatus != HTTP_INTERNAL_SERVER_ERROR) | |
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> | |
1149 | void ConvertTo(Value& value, bool fAllowNull=false) | |
1150 | { | |
1151 | if (fAllowNull && value.type() == null_type) | |
1152 | return; | |
1153 | if (value.type() == str_type) | |
1154 | { | |
1155 | // reinterpret string as unquoted json value | |
1156 | Value value2; | |
1157 | string strJSON = value.get_str(); | |
1158 | if (!read_string(strJSON, value2)) | |
1159 | throw runtime_error(string("Error parsing JSON:")+strJSON); | |
1160 | ConvertTo<T>(value2, fAllowNull); | |
1161 | value = value2; | |
1162 | } | |
1163 | else | |
1164 | { | |
1165 | value = value.get_value<T>(); | |
1166 | } | |
1167 | } | |
1168 | ||
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 | // | |
1181 | if (strMethod == "stop" && n > 0) ConvertTo<bool>(params[0]); | |
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]); | |
1202 | if (strMethod == "getblocktemplate" && n > 0) ConvertTo<Object>(params[0]); | |
1203 | if (strMethod == "listsinceblock" && n > 1) ConvertTo<boost::int64_t>(params[1]); | |
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]); | |
1208 | if (strMethod == "createmultisig" && n > 0) ConvertTo<boost::int64_t>(params[0]); | |
1209 | if (strMethod == "createmultisig" && n > 1) ConvertTo<Array>(params[1]); | |
1210 | if (strMethod == "listunspent" && n > 0) ConvertTo<boost::int64_t>(params[0]); | |
1211 | if (strMethod == "listunspent" && n > 1) ConvertTo<boost::int64_t>(params[1]); | |
1212 | if (strMethod == "listunspent" && n > 2) ConvertTo<Array>(params[2]); | |
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]); | |
1216 | if (strMethod == "signrawtransaction" && n > 1) ConvertTo<Array>(params[1], true); | |
1217 | if (strMethod == "signrawtransaction" && n > 2) ConvertTo<Array>(params[2], true); | |
1218 | if (strMethod == "gettxout" && n > 1) ConvertTo<boost::int64_t>(params[1]); | |
1219 | if (strMethod == "gettxout" && n > 2) ConvertTo<bool>(params[2]); | |
1220 | if (strMethod == "lockunspent" && n > 0) ConvertTo<bool>(params[0]); | |
1221 | if (strMethod == "lockunspent" && n > 1) ConvertTo<Array>(params[1]); | |
1222 | if (strMethod == "importprivkey" && n > 2) ConvertTo<bool>(params[2]); | |
1223 | ||
1224 | return params; | |
1225 | } | |
1226 | ||
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 | |
1246 | std::vector<std::string> strParams(&argv[2], &argv[argc]); | |
1247 | Array params = RPCConvertValues(strMethod, strParams); | |
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"); | |
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 | { | |
1286 | fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); | |
1287 | } | |
1288 | return nRet; | |
1289 | } | |
1290 | ||
1291 | ||
1292 | ||
1293 | ||
1294 | #ifdef TEST | |
1295 | int main(int argc, char *argv[]) | |
1296 | { | |
1297 | #ifdef _MSC_VER | |
1298 | // Turn off Microsoft heap dump noise | |
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 | |
1326 | ||
1327 | const CRPCTable tableRPC; |