Permalink
Browse files

Merge pull request #6388

0937290 doc: mention RPC random cookie authentication in release notes (Wladimir J. van der Laan)
71cbeaa rpc: Implement random-cookie based authentication (Wladimir J. van der Laan)
  • Loading branch information...
laanwj committed Jul 14, 2015
2 parents bb59e78 + 0937290 commit fd5dfda9396968346edcf1f5ddf946d63e797554
Showing with 120 additions and 27 deletions.
  1. +15 −0 doc/release-notes.md
  2. +16 −8 src/bitcoin-cli.cpp
  3. +67 −0 src/rpcprotocol.cpp
  4. +10 −0 src/rpcprotocol.h
  5. +12 −19 src/rpcserver.cpp
View
@@ -4,6 +4,21 @@ release-notes at release time)
Notable changes
===============
+Random-cookie RPC authentication
+---------------------------------
+
+When no `-rpcpassword` is specified, the daemon now uses a special 'cookie'
+file for authentication. This file is generated with random content when the
+daemon starts, and deleted when it exits. Its contents are used as
+authentication token. Read access to this file controls who can access through
+RPC. By default it is stored in the data directory but its location can be
+overridden with the option `-rpccookiefile`.
+
+This is similar to Tor's CookieAuthentication: see
+https://www.torproject.org/docs/tor-manual.html.en
+
+This allows running bitcoind without having to do any manual configuration.
+
Example header
----------------------
View
@@ -97,12 +97,6 @@ static bool AppInitRPC(int argc, char* argv[])
UniValue CallRPC(const string& strMethod, const UniValue& 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().string().c_str()));
-
// Connect to localhost
bool fUseSSL = GetBoolArg("-rpcssl", false);
boost::asio::io_service io_service;
@@ -116,10 +110,24 @@ UniValue CallRPC(const string& strMethod, const UniValue& params)
if (!fConnected)
throw CConnectionFailed("couldn't connect to server");
+ // Find credentials to use
+ std::string strRPCUserColonPass;
+ if (mapArgs["-rpcpassword"] == "") {
+ // Try fall back to cookie-based authentication if no password is provided
+ if (!GetAuthCookie(&strRPCUserColonPass)) {
+ 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().string().c_str()));
+
+ }
+ } else {
+ strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
+ }
+
// HTTP basic authentication
- string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]);
map<string, string> mapRequestHeaders;
- mapRequestHeaders["Authorization"] = string("Basic ") + strUserPass64;
+ mapRequestHeaders["Authorization"] = string("Basic ") + EncodeBase64(strRPCUserColonPass);
// Send request
string strRequest = JSONRPCRequest(strMethod, params, 1);
View
@@ -6,13 +6,15 @@
#include "rpcprotocol.h"
#include "clientversion.h"
+#include "random.h"
#include "tinyformat.h"
#include "util.h"
#include "utilstrencodings.h"
#include "utiltime.h"
#include "version.h"
#include <stdint.h>
+#include <fstream>
#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
@@ -287,3 +289,68 @@ UniValue JSONRPCError(int code, const string& message)
error.push_back(Pair("message", message));
return error;
}
+
+/** Username used when cookie authentication is in use (arbitrary, only for
+ * recognizability in debugging/logging purposes)
+ */
+static const std::string COOKIEAUTH_USER = "__cookie__";
+/** Default name for auth cookie file */
+static const std::string COOKIEAUTH_FILE = ".cookie";
+
+boost::filesystem::path GetAuthCookieFile()
+{
+ boost::filesystem::path path(GetArg("-rpccookiefile", COOKIEAUTH_FILE));
+ if (!path.is_complete()) path = GetDataDir() / path;
+ return path;
+}
+
+bool GenerateAuthCookie(std::string *cookie_out)
+{
+ unsigned char rand_pwd[32];
+ GetRandBytes(rand_pwd, 32);
+ std::string cookie = COOKIEAUTH_USER + ":" + EncodeBase64(&rand_pwd[0],32);
+
+ /** the umask determines what permissions are used to create this file -
+ * these are set to 077 in init.cpp unless overridden with -sysperms.
+ */
+ std::ofstream file;
+ boost::filesystem::path filepath = GetAuthCookieFile();
+ file.open(filepath.string().c_str());
+ if (!file.is_open()) {
+ LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath.string());
+ return false;
+ }
+ file << cookie;
+ file.close();
+ LogPrintf("Generated RPC authentication cookie %s\n", filepath.string());
+
+ if (cookie_out)
+ *cookie_out = cookie;
+ return true;
+}
+
+bool GetAuthCookie(std::string *cookie_out)
+{
+ std::ifstream file;
+ std::string cookie;
+ boost::filesystem::path filepath = GetAuthCookieFile();
+ file.open(filepath.string().c_str());
+ if (!file.is_open())
+ return false;
+ std::getline(file, cookie);
+ file.close();
+
+ if (cookie_out)
+ *cookie_out = cookie;
+ return true;
+}
+
+void DeleteAuthCookie()
+{
+ try {
+ boost::filesystem::remove(GetAuthCookieFile());
+ } catch (const boost::filesystem::filesystem_error& e) {
+ LogPrintf("%s: Unable to remove random auth cookie file: %s\n", __func__, e.what());
+ }
+}
+
View
@@ -14,6 +14,7 @@
#include <boost/iostreams/stream.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
+#include <boost/filesystem.hpp>
#include "univalue/univalue.h"
@@ -165,4 +166,13 @@ UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const Un
std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id);
UniValue JSONRPCError(int code, const std::string& message);
+/** Get name of RPC authentication cookie file */
+boost::filesystem::path GetAuthCookieFile();
+/** Generate a new RPC authentication cookie and write it to disk */
+bool GenerateAuthCookie(std::string *cookie_out);
+/** Read the RPC authentication cookie from disk */
+bool GetAuthCookie(std::string *cookie_out);
+/** Delete RPC authentication cookie from disk */
+void DeleteAuthCookie();
+
#endif // BITCOIN_RPCPROTOCOL_H
View
@@ -597,27 +597,18 @@ void StartRPCThreads()
strAllowed += subnet.ToString() + " ";
LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed);
- strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
if (mapArgs["-rpcpassword"] == "")
{
- unsigned char rand_pwd[32];
- GetRandBytes(rand_pwd, 32);
- uiInterface.ThreadSafeMessageBox(strprintf(
- _("To use bitcoind, or the -server option to bitcoin-qt, you must set an rpcpassword in the configuration file:\n"
- "%s\n"
- "It is recommended you use the following random password:\n"
- "rpcuser=bitcoinrpc\n"
- "rpcpassword=%s\n"
- "(you do not need to remember this password)\n"
- "The username and password MUST NOT be the same.\n"
- "If the file does not exist, create it with owner-readable-only file permissions.\n"
- "It is also recommended to set alertnotify so you are notified of problems;\n"
- "for example: alertnotify=echo %%s | mail -s \"Bitcoin Alert\" admin@foo.com\n"),
- GetConfigFile().string(),
- EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32)),
- "", CClientUIInterface::MSG_ERROR | CClientUIInterface::SECURE);
- StartShutdown();
- return;
+ LogPrintf("No rpcpassword set - using random cookie authentication\n");
+ if (!GenerateAuthCookie(&strRPCUserColonPass)) {
+ uiInterface.ThreadSafeMessageBox(
+ _("Error: A fatal internal error occured, see debug.log for details"), // Same message as AbortNode
+ "", CClientUIInterface::MSG_ERROR);
+ StartShutdown();
+ return;
+ }
+ } else {
+ strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
}
assert(rpc_io_service == NULL);
@@ -768,6 +759,8 @@ void StopRPCThreads()
}
deadlineTimers.clear();
+ DeleteAuthCookie();
+
rpc_io_service->stop();
g_rpcSignals.Stopped();
if (rpc_worker_group != NULL)

0 comments on commit fd5dfda

Please sign in to comment.