diff options
author | Gavin Andresen <gavinandresen@gmail.com> | 2010-07-23 13:58:39 +0000 |
---|---|---|
committer | Gavin Andresen <gavinandresen@gmail.com> | 2010-07-23 13:58:39 +0000 |
commit | fe98cf8dc5066368b78e9ce208118c3532598dd2 (patch) | |
tree | f5cbaf0e637fffa558ed92b7d3a2a5e790696d35 | |
parent | e96cd730d188dde8915061ad4f19dc716dd97097 (diff) |
Configuration file, HTTP Basic authentication
-rw-r--r-- | headers.h | 4 | ||||
-rw-r--r-- | init.cpp | 85 | ||||
-rw-r--r-- | makefile.mingw | 2 | ||||
-rw-r--r-- | makefile.osx | 1 | ||||
-rw-r--r-- | makefile.unix | 2 | ||||
-rw-r--r-- | makefile.vc | 2 | ||||
-rw-r--r-- | rpc.cpp | 284 | ||||
-rw-r--r-- | util.cpp | 39 | ||||
-rw-r--r-- | util.h | 11 |
9 files changed, 315 insertions, 115 deletions
@@ -26,6 +26,7 @@ #include <wx/clipbrd.h> #include <wx/taskbar.h> #endif +#include <openssl/buffer.h> #include <openssl/ecdsa.h> #include <openssl/evp.h> #include <openssl/rand.h> @@ -64,6 +65,9 @@ #include <boost/interprocess/sync/interprocess_recursive_mutex.hpp> #include <boost/date_time/gregorian/gregorian_types.hpp> #include <boost/date_time/posix_time/posix_time_types.hpp> +#include <boost/config.hpp> +#include <boost/program_options/detail/config_file.hpp> +#include <boost/program_options/parsers.hpp> #ifdef __WXMSW__ #include <windows.h> @@ -240,33 +240,34 @@ IMPLEMENT_APP(CMyApp) bool CMyApp::Initialize(int& argc, wxChar** argv) { - if (argc > 1 && argv[1][0] != '-' && (!fWindows || argv[1][0] != '/') && - wxString(argv[1]) != "start") - { - fCommandLine = true; - } - else if (!fGUI) - { - fDaemon = true; - } - else + for (int i = 1; i < argc; i++) + if (!IsSwitchChar(argv[i][0])) + fCommandLine = true; + + if (!fCommandLine) { - // wxApp::Initialize will remove environment-specific parameters, - // so it's too early to call ParseParameters yet - for (int i = 1; i < argc; i++) + if (!fGUI) + { + fDaemon = true; + } + else { - wxString str = argv[i]; - #ifdef __WXMSW__ - if (str.size() >= 1 && str[0] == '/') - str[0] = '-'; - char pszLower[MAX_PATH]; - strlcpy(pszLower, str.c_str(), sizeof(pszLower)); - strlwr(pszLower); - str = pszLower; - #endif - // haven't decided which argument to use for this yet - if (str == "-daemon" || str == "-d" || str == "start") - fDaemon = true; + // wxApp::Initialize will remove environment-specific parameters, + // so it's too early to call ParseParameters yet + for (int i = 1; i < argc; i++) + { + wxString str = argv[i]; + #ifdef __WXMSW__ + if (str.size() >= 1 && str[0] == '/') + str[0] = '-'; + char pszLower[MAX_PATH]; + strlcpy(pszLower, str.c_str(), sizeof(pszLower)); + strlwr(pszLower); + str = pszLower; + #endif + if (str == "-daemon") + fDaemon = true; + } } } @@ -375,22 +376,23 @@ bool CMyApp::OnInit2() // // Parameters // - if (fCommandLine) - { - int ret = CommandLineRPC(argc, argv); - exit(ret); - } - ParseParameters(argc, argv); + + if (mapArgs.count("-datadir")) + strlcpy(pszSetDataDir, mapArgs["-datadir"].c_str(), sizeof(pszSetDataDir)); + + ReadConfigFile(mapArgs, mapMultiArgs); // Must be done after processing datadir + if (mapArgs.count("-?") || mapArgs.count("--help")) { wxString strUsage = string() + _("Usage:") + "\t\t\t\t\t\t\t\t\t\t\n" + - " bitcoin [options] \t" + "\n" + - " bitcoin [command] \t" + _("Send command to bitcoin running with -server or -daemon\n") + - " bitcoin [command] -? \t" + _("Get help for a command\n") + - " bitcoin help <pw> \t" + _("List commands\n") + + " bitcoin [options] \t " + "\n" + + " bitcoin [options] <command> [params]\t " + _("Send command to -server or bitcoind\n") + + " bitcoin [options] <command> -? \t\t " + _("Get help for a command\n") + + " bitcoin help \t\t\t " + _("List commands\n") + _("Options:\n") + + " -conf=<file> \t " + _("Specify configuration file (default: bitcoin.conf)\n") + " -gen \t " + _("Generate coins\n") + " -gen=0 \t " + _("Don't generate coins\n") + " -min \t " + _("Start minimized\n") + @@ -398,7 +400,7 @@ bool CMyApp::OnInit2() " -proxy=<ip:port>\t " + _("Connect through socks4 proxy\n") + " -addnode=<ip> \t " + _("Add a node to connect to\n") + " -connect=<ip> \t " + _("Connect only to the specified node\n") + - " -rpcpw=<pw> \t " + _("Accept command line and JSON-RPC commands with the given password\n") + + " -server \t " + _("Accept command line and JSON-RPC commands\n") + " -daemon \t " + _("Run in the background as a daemon and accept commands\n") + " -? \t " + _("This help message\n"); @@ -413,15 +415,18 @@ bool CMyApp::OnInit2() return false; } - if (mapArgs.count("-datadir")) - strlcpy(pszSetDataDir, mapArgs["-datadir"].c_str(), sizeof(pszSetDataDir)); - if (mapArgs.count("-debug")) fDebug = true; if (mapArgs.count("-printtodebugger")) fPrintToDebugger = true; + if (fCommandLine) + { + int ret = CommandLineRPC(argc, argv); + exit(ret); + } + if (!fDebug && !pszSetDataDir[0]) ShrinkDebugFile(); printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); @@ -611,7 +616,7 @@ bool CMyApp::OnInit2() if (!CreateThread(StartNode, NULL)) wxMessageBox("Error: CreateThread(StartNode) failed", "Bitcoin"); - if (mapArgs.count("-server") || mapArgs.count("-rpcpw") || fDaemon) + if (mapArgs.count("-server") || fDaemon) CreateThread(ThreadRPCServer, NULL); if (fFirstRun) diff --git a/makefile.mingw b/makefile.mingw index 35a63a847a..5d698809c9 100644 --- a/makefile.mingw +++ b/makefile.mingw @@ -22,7 +22,7 @@ WXLIBS= \ -l wxmsw29ud_html -l wxmsw29ud_core -l wxmsw29ud_adv -l wxbase29ud -l wxtiffd -l wxjpegd -l wxpngd -l wxzlibd LIBS= \ - -l libboost_system-mgw34-mt-d -l libboost_filesystem-mgw34-mt-d \ + -l libboost_system-mgw34-mt-d -l libboost_filesystem-mgw34-mt-d -l libboost_program_options-mgw34-mt-d \ -l db_cxx \ -l eay32 \ -l kernel32 -l user32 -l gdi32 -l comdlg32 -l winspool -l winmm -l shell32 -l comctl32 -l ole32 -l oleaut32 -l uuid -l rpcrt4 -l advapi32 -l ws2_32 -l shlwapi diff --git a/makefile.osx b/makefile.osx index f624d922ac..4b6549b372 100644 --- a/makefile.osx +++ b/makefile.osx @@ -19,6 +19,7 @@ LIBS= -dead_strip \ $(DEPSDIR)/lib/libdb_cxx-4.8.a \ $(DEPSDIR)/lib/libboost_system.a \ $(DEPSDIR)/lib/libboost_filesystem.a \ + $(DEPSDIR)/lib/libboost_program_options.a \ $(DEPSDIR)/lib/libcrypto.a WXDEFS=$(shell $(DEPSDIR)/bin/wx-config --cxxflags) -DNOPCH -DMSG_NOSIGNAL=0 diff --git a/makefile.unix b/makefile.unix index 6280a74951..8b6441ef16 100644 --- a/makefile.unix +++ b/makefile.unix @@ -21,7 +21,7 @@ WXLIBS= \ LIBS= \ -Wl,-Bstatic \ - -l boost_system -l boost_filesystem \ + -l boost_system -l boost_filesystem -l boost_program_options \ -l db_cxx \ -l crypto \ -Wl,-Bdynamic \ diff --git a/makefile.vc b/makefile.vc index 14e6e39051..960551fbf0 100644 --- a/makefile.vc +++ b/makefile.vc @@ -19,7 +19,7 @@ LIBPATHS= \ /LIBPATH:"/wxwidgets/lib/vc_lib" LIBS= \ - libboost_system-vc80-mt-gd.lib libboost_filesystem-vc80-mt-gd.lib \ + libboost_system-vc80-mt-gd.lib libboost_filesystem-vc80-mt-gd.lib libboost_program_options-vc80-mt-gd.lib \ libdb47sd.lib \ libeay32.lib \ wxmsw29ud_html.lib wxmsw29ud_core.lib wxmsw29ud_adv.lib wxbase29ud.lib wxtiffd.lib wxjpegd.lib wxpngd.lib wxzlibd.lib \ @@ -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]; @@ -416,7 +416,7 @@ void ParseParameters(int argc, char* argv[]) { mapArgs.clear(); mapMultiArgs.clear(); - for (int i = 0; i < argc; i++) + for (int i = 1; i < argc; i++) { char psz[10000]; strlcpy(psz, argv[i], sizeof(psz)); @@ -431,6 +431,8 @@ void ParseParameters(int argc, char* argv[]) if (psz[0] == '/') psz[0] = '-'; #endif + if (psz[0] != '-') + break; mapArgs[psz] = pszValue; mapMultiArgs[psz].push_back(pszValue); } @@ -619,6 +621,38 @@ string GetDataDir() return pszDir; } +string GetConfigFile() +{ + namespace fs = boost::filesystem; + fs::path pathConfig(mapArgs.count("-conf") ? mapArgs["-conf"] : string("bitcoin.conf")); + if (!pathConfig.is_complete()) + pathConfig = fs::path(GetDataDir()) / pathConfig; + return pathConfig.string(); +} + +void ReadConfigFile(map<string, string>& mapSettingsRet, + map<string, vector<string> >& mapMultiSettingsRet) +{ + namespace fs = boost::filesystem; + namespace pod = boost::program_options::detail; + + fs::ifstream streamConfig(GetConfigFile()); + if (!streamConfig.good()) + return; + + set<string> setOptions; + setOptions.insert("*"); + + for (pod::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it) + { + // Don't overwrite existing settings so command line settings override bitcoin.conf + string strKey = string("-") + it->string_key; + if (mapSettingsRet.count(strKey) == 0) + mapSettingsRet[strKey] = it->value[0]; + mapMultiSettingsRet[strKey].push_back(it->value[0]); + } +} + int GetFilesize(FILE* file) { int nSavePos = ftell(file); @@ -656,9 +690,6 @@ void ShrinkDebugFile() - - - // // "Never go to sea with two chronometers; take one or three." // Our three chronometers are: @@ -141,6 +141,8 @@ void ParseParameters(int argc, char* argv[]); const char* wxGetTranslation(const char* psz); int GetFilesize(FILE* file); void GetDataDir(char* pszDirRet); +string GetConfigFile(); +void ReadConfigFile(map<string, string>& mapSettingsRet, map<string, vector<string> >& mapMultiSettingsRet); #ifdef __WXMSW__ string MyGetSpecialFolderPath(int nFolder, bool fCreate); #endif @@ -348,7 +350,14 @@ void skipspaces(T& it) ++it; } - +inline bool IsSwitchChar(char c) +{ +#ifdef __WXMSW__ + return c == '-' || c == '/'; +#else + return c == '-'; +#endif +} |