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