diff options
author | s_nakamoto <s_nakamoto@1a98c847-1fd6-4fd8-948a-caf3550aa51b> | 2010-07-25 16:45:21 +0000 |
---|---|---|
committer | s_nakamoto <s_nakamoto@1a98c847-1fd6-4fd8-948a-caf3550aa51b> | 2010-07-25 16:45:21 +0000 |
commit | 3b7cd5d89a226426df9c723d1f9ddfe08b7d1def (patch) | |
tree | cb956c8dd87690dcd9e5d554286d39edbb199c32 /rpc.cpp | |
parent | e8bff10f07b80d820545661cee9337d8664a64cb (diff) | |
download | bitcoin-3b7cd5d89a226426df9c723d1f9ddfe08b7d1def.tar.xz |
Gavin Andresen's JSON-RPC HTTP authentication,
faster initial block download
-- version 0.3.3
git-svn-id: https://bitcoin.svn.sourceforge.net/svnroot/bitcoin/trunk@109 1a98c847-1fd6-4fd8-948a-caf3550aa51b
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, "%s", 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];
|