diff options
Diffstat (limited to 'rpc.cpp')
-rw-r--r-- | rpc.cpp | 284 |
1 files changed, 217 insertions, 67 deletions
@@ -21,7 +21,27 @@ void ThreadRPCServer2(void* parg); typedef Value(*rpcfn_type)(const Array& params, bool fHelp); extern map<string, rpcfn_type> mapCallTable; -static string strRPCPassword; + + +void PrintConsole(const char* format, ...) +{ + char buffer[50000]; + int limit = sizeof(buffer); + va_list arg_ptr; + va_start(arg_ptr, format); + int ret = _vsnprintf(buffer, limit, format, arg_ptr); + va_end(arg_ptr); + if (ret < 0 || ret >= limit) + { + ret = limit - 1; + buffer[limit-1] = 0; + } +#if defined(__WXMSW__) && wxUSE_GUI + MyMessageBox(buffer, "Bitcoin", wxOK | wxICON_EXCLAMATION); +#else + fprintf(stdout, buffer); +#endif +} @@ -34,12 +54,11 @@ static string strRPCPassword; /// - Value help(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( - "help <pw>\n" + "help\n" "List commands."); string strRet; @@ -76,7 +95,7 @@ Value stop(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( - "stop <pw>\n" + "stop\n" "Stop bitcoin server."); // Shutdown will take long enough that the response should get back @@ -89,7 +108,7 @@ Value getblockcount(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( - "getblockcount <pw>\n" + "getblockcount\n" "Returns the number of blocks in the longest block chain."); return nBestHeight + 1; @@ -100,7 +119,7 @@ Value getblocknumber(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( - "getblocknumber <pw>\n" + "getblocknumber\n" "Returns the block number of the latest block in the longest block chain."); return nBestHeight; @@ -111,7 +130,7 @@ Value getconnectioncount(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( - "getconnectioncount <pw>\n" + "getconnectioncount\n" "Returns the number of connections to other nodes."); return (int)vNodes.size(); @@ -134,7 +153,7 @@ Value getdifficulty(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( - "getdifficulty <pw>\n" + "getdifficulty\n" "Returns the proof-of-work difficulty as a multiple of the minimum difficulty."); return GetDifficulty(); @@ -145,7 +164,7 @@ Value getbalance(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( - "getbalance <pw>\n" + "getbalance\n" "Returns the server's available balance."); return ((double)GetBalance() / (double)COIN); @@ -156,7 +175,7 @@ Value getgenerate(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( - "getgenerate <pw>\n" + "getgenerate\n" "Returns true or false."); return (bool)fGenerateBitcoins; @@ -167,7 +186,7 @@ Value setgenerate(const Array& params, bool fHelp) { if (fHelp || params.size() < 1 || params.size() > 2) throw runtime_error( - "setgenerate <pw> <generate> [genproclimit]\n" + "setgenerate <generate> [genproclimit]\n" "<generate> is true or false to turn generation on or off.\n" "Generation is limited to [genproclimit] processors, -1 is unlimited."); @@ -193,7 +212,7 @@ Value getinfo(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( - "getinfo <pw>"); + "getinfo"); Object obj; obj.push_back(Pair("balance", (double)GetBalance() / (double)COIN)); @@ -211,7 +230,7 @@ Value getnewaddress(const Array& params, bool fHelp) { if (fHelp || params.size() > 1) throw runtime_error( - "getnewaddress <pw> [label]\n" + "getnewaddress [label]\n" "Returns a new bitcoin address for receiving payments. " "If [label] is specified (recommended), it is added to the address book " "so payments received with the address will be labeled."); @@ -233,7 +252,7 @@ Value setlabel(const Array& params, bool fHelp) { if (fHelp || params.size() < 1 || params.size() > 2) throw runtime_error( - "setlabel <pw> <bitcoinaddress> <label>\n" + "setlabel <bitcoinaddress> <label>\n" "Sets the label associated with the given address."); string strAddress = params[0].get_str(); @@ -250,7 +269,7 @@ Value getlabel(const Array& params, bool fHelp) { if (fHelp || params.size() != 1) throw runtime_error( - "getlabel <pw> <bitcoinaddress>\n" + "getlabel <bitcoinaddress>\n" "Returns the label associated with the given address."); string strAddress = params[0].get_str(); @@ -270,7 +289,7 @@ Value getaddressesbylabel(const Array& params, bool fHelp) { if (fHelp || params.size() != 1) throw runtime_error( - "getaddressesbylabel <pw> <label>\n" + "getaddressesbylabel <label>\n" "Returns the list of addresses with the given label."); string strLabel = params[0].get_str(); @@ -300,7 +319,7 @@ Value sendtoaddress(const Array& params, bool fHelp) { if (fHelp || params.size() < 2 || params.size() > 4) throw runtime_error( - "sendtoaddress <pw> <bitcoinaddress> <amount> [comment] [comment-to]\n" + "sendtoaddress <bitcoinaddress> <amount> [comment] [comment-to]\n" "<amount> is a real and is rounded to the nearest 0.01"); string strAddress = params[0].get_str(); @@ -328,7 +347,7 @@ Value listtransactions(const Array& params, bool fHelp) { if (fHelp || params.size() > 2) throw runtime_error( - "listtransactions <pw> [count=10] [includegenerated=false]\n" + "listtransactions [count=10] [includegenerated=false]\n" "Returns up to [count] most recent transactions."); int64 nCount = 10; @@ -349,7 +368,7 @@ Value getreceivedbyaddress(const Array& params, bool fHelp) { if (fHelp || params.size() < 1 || params.size() > 2) throw runtime_error( - "getreceivedbyaddress <pw> <bitcoinaddress> [minconf=1]\n" + "getreceivedbyaddress <bitcoinaddress> [minconf=1]\n" "Returns the total amount received by <bitcoinaddress> in transactions with at least [minconf] confirmations."); // Bitcoin address @@ -390,7 +409,7 @@ Value getreceivedbylabel(const Array& params, bool fHelp) { if (fHelp || params.size() < 1 || params.size() > 2) throw runtime_error( - "getreceivedbylabel <pw> <label> [minconf=1]\n" + "getreceivedbylabel <label> [minconf=1]\n" "Returns the total amount received by addresses with <label> in transactions with at least [minconf] confirmations."); // Get the set of pub keys that have the label @@ -553,7 +572,7 @@ Value listreceivedbyaddress(const Array& params, bool fHelp) { if (fHelp || params.size() > 2) throw runtime_error( - "listreceivedbyaddress <pw> [minconf=1] [includeempty=false]\n" + "listreceivedbyaddress [minconf=1] [includeempty=false]\n" "[minconf] is the minimum number of confirmations before payments are included.\n" "[includeempty] whether to include addresses that haven't received any payments.\n" "Returns an array of objects containing:\n" @@ -569,7 +588,7 @@ Value listreceivedbylabel(const Array& params, bool fHelp) { if (fHelp || params.size() > 2) throw runtime_error( - "listreceivedbylabel <pw> [minconf=1] [includeempty=false]\n" + "listreceivedbylabel [minconf=1] [includeempty=false]\n" "[minconf] is the minimum number of confirmations before payments are included.\n" "[includeempty] whether to include labels that haven't received any payments.\n" "Returns an array of objects containing:\n" @@ -632,23 +651,41 @@ map<string, rpcfn_type> mapCallTable(pCallTable, pCallTable + sizeof(pCallTable) // and to be compatible with other JSON-RPC implementations. // -string HTTPPost(const string& strMsg) +string HTTPPost(const string& strMsg, const map<string,string>& mapRequestHeaders) { - return strprintf( - "POST / HTTP/1.1\r\n" - "User-Agent: json-rpc/1.0\r\n" - "Host: 127.0.0.1\r\n" - "Content-Type: application/json\r\n" - "Content-Length: %d\r\n" - "Accept: application/json\r\n" - "\r\n" - "%s", - strMsg.size(), - strMsg.c_str()); + ostringstream s; + s << "POST / HTTP/1.1\r\n" + << "User-Agent: json-rpc/1.0\r\n" + << "Host: 127.0.0.1\r\n" + << "Content-Type: application/json\r\n" + << "Content-Length: " << strMsg.size() << "\r\n" + << "Accept: application/json\r\n"; + for (map<string,string>::const_iterator it = mapRequestHeaders.begin(); it != mapRequestHeaders.end(); ++it) + s << it->first << ": " << it->second << "\r\n"; + s << "\r\n" << strMsg; + + return s.str(); } string HTTPReply(const string& strMsg, int nStatus=200) { + if (nStatus == 401) + return "HTTP/1.0 401 Authorization Required\r\n" + "Server: HTTPd/1.0\r\n" + "Date: Sat, 08 Jul 2006 12:04:08 GMT\r\n" + "WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 311\r\n" + "\r\n" + "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n" + "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n" + "<HTML>\r\n" + "<HEAD>\r\n" + "<TITLE>Error</TITLE>\r\n" + "<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n" + "</HEAD>\r\n" + "<BODY><H1>401 Unauthorized.</H1></BODY>\r\n" + "</HTML>\r\n"; string strStatus; if (nStatus == 200) strStatus = "OK"; if (nStatus == 500) strStatus = "Internal Server Error"; @@ -667,7 +704,17 @@ string HTTPReply(const string& strMsg, int nStatus=200) strMsg.c_str()); } -int ReadHTTPHeader(tcp::iostream& stream) +int ReadHTTPStatus(tcp::iostream& stream) +{ + string str; + getline(stream, str); + vector<string> vWords; + boost::split(vWords, str, boost::is_any_of(" ")); + int nStatus = atoi(vWords[1].c_str()); + return nStatus; +} + +int ReadHTTPHeader(tcp::iostream& stream, map<string, string>& mapHeadersRet) { int nLen = 0; loop @@ -676,26 +723,92 @@ int ReadHTTPHeader(tcp::iostream& stream) std::getline(stream, str); if (str.empty() || str == "\r") break; - if (str.substr(0,15) == "Content-Length:") - nLen = atoi(str.substr(15)); + string::size_type nColon = str.find(":"); + if (nColon != string::npos) + { + string strHeader = str.substr(0, nColon); + boost::trim(strHeader); + string strValue = str.substr(nColon+1); + boost::trim(strValue); + mapHeadersRet[strHeader] = strValue; + if (strHeader == "Content-Length") + nLen = atoi(strValue.c_str()); + } } return nLen; } -inline string ReadHTTP(tcp::iostream& stream) +int ReadHTTP(tcp::iostream& stream, map<string, string>& mapHeadersRet, string& strMessageRet) { + mapHeadersRet.clear(); + strMessageRet = ""; + + // Read status + int nStatus = ReadHTTPStatus(stream); + // Read header - int nLen = ReadHTTPHeader(stream); + int nLen = ReadHTTPHeader(stream, mapHeadersRet); if (nLen <= 0) - return string(); + return 500; // Read message vector<char> vch(nLen); stream.read(&vch[0], nLen); - return string(vch.begin(), vch.end()); + strMessageRet = string(vch.begin(), vch.end()); + + return nStatus; } +string EncodeBase64(string s) +{ + BIO *b64, *bmem; + BUF_MEM *bptr; + + b64 = BIO_new(BIO_f_base64()); + bmem = BIO_new(BIO_s_mem()); + b64 = BIO_push(b64, bmem); + BIO_write(b64, s.c_str(), s.size()); + BIO_flush(b64); + BIO_get_mem_ptr(b64, &bptr); + string result(bptr->data, bptr->length-1); + BIO_free_all(b64); + + return result; +} + +string DecodeBase64(string s) +{ + BIO *b64, *bmem; + + char* buffer = static_cast<char*>(calloc(s.size(), sizeof(char))); + + b64 = BIO_new(BIO_f_base64()); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + bmem = BIO_new_mem_buf(const_cast<char*>(s.c_str()), s.size()); + bmem = BIO_push(b64, bmem); + BIO_read(bmem, buffer, s.size()); + BIO_free_all(bmem); + + string result(buffer); + free(buffer); + return result; +} + +bool HTTPAuthorized(map<string, string>& mapHeaders) +{ + string strAuth = mapHeaders["Authorization"]; + if (strAuth.substr(0,6) != "Basic ") + return false; + string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64); + string strUserPass = DecodeBase64(strUserPass64); + string::size_type nColon = strUserPass.find(":"); + if (nColon == string::npos) + return false; + string strUser = strUserPass.substr(0, nColon); + string strPassword = strUserPass.substr(nColon+1); + return (strUser == mapArgs["-rpcuser"] && strPassword == mapArgs["-rpcpassword"]); +} // // JSON-RPC protocol @@ -751,15 +864,20 @@ void ThreadRPCServer2(void* parg) { printf("ThreadRPCServer started\n"); - if (mapArgs.count("-rpcpw")) - strRPCPassword = mapArgs["-rpcpw"]; - if (strRPCPassword == "") + if (mapArgs["-rpcuser"] == "" && mapArgs["-rpcpassword"] == "") { -#if defined(__WXMSW__) && wxUSE_GUI - MyMessageBox("Warning: rpc password is blank, use -rpcpw=<password>\n", "Bitcoin", wxOK | wxICON_EXCLAMATION); -#else - fprintf(stdout, "Warning: rpc password is blank, use -rpcpw=<password>\n"); -#endif + string strWhatAmI = "To use bitcoind"; + if (mapArgs.count("-server")) + strWhatAmI = strprintf(_("To use the %s option"), "\"-server\""); + else if (mapArgs.count("-daemon")) + strWhatAmI = strprintf(_("To use the %s option"), "\"-daemon\""); + PrintConsole( + _("Warning: %s, you must set rpcpassword=<password>\nin the configuration file: %s\n" + "If the file does not exist, create it with owner-readable-only file permissions.\n"), + strWhatAmI.c_str(), + GetConfigFile().c_str()); + CreateThread(Shutdown, NULL); + return; } // Bind to loopback 127.0.0.1 so the socket can only be accessed locally @@ -783,7 +901,26 @@ void ThreadRPCServer2(void* parg) continue; // Receive request - string strRequest = ReadHTTP(stream); + map<string, string> mapHeaders; + string strRequest; + ReadHTTP(stream, mapHeaders, strRequest); + + // Check authorization + if (mapHeaders.count("Authorization") == 0) + { + stream << HTTPReply("", 401) << std::flush; + continue; + } + if (!HTTPAuthorized(mapHeaders)) + { + // Deter brute-forcing short passwords + if (mapArgs["-rpcpassword"].size() < 15) + Sleep(50); + + stream << HTTPReply("", 401) << std::flush; + printf("ThreadRPCServer incorrect password attempt\n"); + continue; + } // Handle multiple invocations per request string::iterator begin = strRequest.begin(); @@ -808,23 +945,11 @@ void ThreadRPCServer2(void* parg) printf("ThreadRPCServer method=%s\n", strMethod.c_str()); - // Check password - if (params.size() < 1 || params[0].type() != str_type) - throw runtime_error("First parameter must be the password."); - if (params[0].get_str() != strRPCPassword) - { - if (strRPCPassword.size() < 15) - Sleep(50); - begin = strRequest.end(); - printf("ThreadRPCServer incorrect password attempt\n"); - throw runtime_error("Incorrect password."); - } - // Execute map<string, rpcfn_type>::iterator mi = mapCallTable.find(strMethod); if (mi == mapCallTable.end()) throw runtime_error("Method not found."); - Value result = (*(*mi).second)(Array(params.begin()+1, params.end()), false); + Value result = (*(*mi).second)(params, false); // Send reply string strReply = JSONRPCReply(result, Value::null, id); @@ -847,18 +972,36 @@ void ThreadRPCServer2(void* parg) Value CallRPC(const string& strMethod, const Array& params) { + if (mapArgs["-rpcuser"] == "" && mapArgs["-rpcpassword"] == "") + throw runtime_error(strprintf( + _("You must set rpcpassword=<password> in the configuration file:\n%s\n" + "If the file does not exist, create it with owner-readable-only file permissions."), + GetConfigFile().c_str())); + // Connect to localhost tcp::iostream stream("127.0.0.1", "8332"); if (stream.fail()) throw runtime_error("couldn't connect to server"); + // HTTP basic authentication + string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]); + map<string, string> mapRequestHeaders; + mapRequestHeaders["Authorization"] = string("Basic ") + strUserPass64; + // Send request string strRequest = JSONRPCRequest(strMethod, params, 1); - stream << HTTPPost(strRequest) << std::flush; + string strPost = HTTPPost(strRequest, mapRequestHeaders); + stream << strPost << std::flush; // Receive reply - string strReply = ReadHTTP(stream); - if (strReply.empty()) + map<string, string> mapHeaders; + string strReply; + int nStatus = ReadHTTP(stream, mapHeaders, strReply); + if (nStatus == 401) + throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); + else if (nStatus >= 400 && nStatus != 500) + throw runtime_error(strprintf("server returned HTTP error %d", nStatus)); + else if (strReply.empty()) throw runtime_error("no response from server"); // Parse reply @@ -904,7 +1047,14 @@ int CommandLineRPC(int argc, char *argv[]) { try { - // Check that method exists + // Skip switches + while (argc > 1 && IsSwitchChar(argv[1][0])) + { + argc--; + argv++; + } + + // Check that the method exists if (argc < 2) throw runtime_error("too few parameters"); string strMethod = argv[1]; |