Skip to content

Commit 71cbeaa

Browse files
committed
rpc: Implement random-cookie based authentication
When no `-rpcpassword` is specified, use a special 'cookie' file for authentication. This file is generated with random content when the daemon starts, and deleted when it exits. Read access to this file controls who can access through RPC. By default this file is stored in the data directory but it be overriden with `-rpccookiefile`. This is similar to Tor CookieAuthentication: see https://www.torproject.org/docs/tor-manual.html.en Alternative to #6258. Like that pull, this allows running bitcoind without any manual configuration. However, daemons should ideally never write to their configuration files, so I prefer this solution.
1 parent 3d9362d commit 71cbeaa

File tree

4 files changed

+105
-27
lines changed

4 files changed

+105
-27
lines changed

src/bitcoin-cli.cpp

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,6 @@ static bool AppInitRPC(int argc, char* argv[])
9797

9898
UniValue CallRPC(const string& strMethod, const UniValue& params)
9999
{
100-
if (mapArgs["-rpcuser"] == "" && mapArgs["-rpcpassword"] == "")
101-
throw runtime_error(strprintf(
102-
_("You must set rpcpassword=<password> in the configuration file:\n%s\n"
103-
"If the file does not exist, create it with owner-readable-only file permissions."),
104-
GetConfigFile().string().c_str()));
105-
106100
// Connect to localhost
107101
bool fUseSSL = GetBoolArg("-rpcssl", false);
108102
boost::asio::io_service io_service;
@@ -116,10 +110,24 @@ UniValue CallRPC(const string& strMethod, const UniValue& params)
116110
if (!fConnected)
117111
throw CConnectionFailed("couldn't connect to server");
118112

113+
// Find credentials to use
114+
std::string strRPCUserColonPass;
115+
if (mapArgs["-rpcpassword"] == "") {
116+
// Try fall back to cookie-based authentication if no password is provided
117+
if (!GetAuthCookie(&strRPCUserColonPass)) {
118+
throw runtime_error(strprintf(
119+
_("You must set rpcpassword=<password> in the configuration file:\n%s\n"
120+
"If the file does not exist, create it with owner-readable-only file permissions."),
121+
GetConfigFile().string().c_str()));
122+
123+
}
124+
} else {
125+
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
126+
}
127+
119128
// HTTP basic authentication
120-
string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]);
121129
map<string, string> mapRequestHeaders;
122-
mapRequestHeaders["Authorization"] = string("Basic ") + strUserPass64;
130+
mapRequestHeaders["Authorization"] = string("Basic ") + EncodeBase64(strRPCUserColonPass);
123131

124132
// Send request
125133
string strRequest = JSONRPCRequest(strMethod, params, 1);

src/rpcprotocol.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
#include "rpcprotocol.h"
77

88
#include "clientversion.h"
9+
#include "random.h"
910
#include "tinyformat.h"
1011
#include "util.h"
1112
#include "utilstrencodings.h"
1213
#include "utiltime.h"
1314
#include "version.h"
1415

1516
#include <stdint.h>
17+
#include <fstream>
1618

1719
#include <boost/algorithm/string.hpp>
1820
#include <boost/asio.hpp>
@@ -287,3 +289,68 @@ UniValue JSONRPCError(int code, const string& message)
287289
error.push_back(Pair("message", message));
288290
return error;
289291
}
292+
293+
/** Username used when cookie authentication is in use (arbitrary, only for
294+
* recognizability in debugging/logging purposes)
295+
*/
296+
static const std::string COOKIEAUTH_USER = "__cookie__";
297+
/** Default name for auth cookie file */
298+
static const std::string COOKIEAUTH_FILE = ".cookie";
299+
300+
boost::filesystem::path GetAuthCookieFile()
301+
{
302+
boost::filesystem::path path(GetArg("-rpccookiefile", COOKIEAUTH_FILE));
303+
if (!path.is_complete()) path = GetDataDir() / path;
304+
return path;
305+
}
306+
307+
bool GenerateAuthCookie(std::string *cookie_out)
308+
{
309+
unsigned char rand_pwd[32];
310+
GetRandBytes(rand_pwd, 32);
311+
std::string cookie = COOKIEAUTH_USER + ":" + EncodeBase64(&rand_pwd[0],32);
312+
313+
/** the umask determines what permissions are used to create this file -
314+
* these are set to 077 in init.cpp unless overridden with -sysperms.
315+
*/
316+
std::ofstream file;
317+
boost::filesystem::path filepath = GetAuthCookieFile();
318+
file.open(filepath.string().c_str());
319+
if (!file.is_open()) {
320+
LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath.string());
321+
return false;
322+
}
323+
file << cookie;
324+
file.close();
325+
LogPrintf("Generated RPC authentication cookie %s\n", filepath.string());
326+
327+
if (cookie_out)
328+
*cookie_out = cookie;
329+
return true;
330+
}
331+
332+
bool GetAuthCookie(std::string *cookie_out)
333+
{
334+
std::ifstream file;
335+
std::string cookie;
336+
boost::filesystem::path filepath = GetAuthCookieFile();
337+
file.open(filepath.string().c_str());
338+
if (!file.is_open())
339+
return false;
340+
std::getline(file, cookie);
341+
file.close();
342+
343+
if (cookie_out)
344+
*cookie_out = cookie;
345+
return true;
346+
}
347+
348+
void DeleteAuthCookie()
349+
{
350+
try {
351+
boost::filesystem::remove(GetAuthCookieFile());
352+
} catch (const boost::filesystem::filesystem_error& e) {
353+
LogPrintf("%s: Unable to remove random auth cookie file: %s\n", __func__, e.what());
354+
}
355+
}
356+

src/rpcprotocol.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <boost/iostreams/stream.hpp>
1515
#include <boost/asio.hpp>
1616
#include <boost/asio/ssl.hpp>
17+
#include <boost/filesystem.hpp>
1718

1819
#include "univalue/univalue.h"
1920

@@ -165,4 +166,13 @@ UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const Un
165166
std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id);
166167
UniValue JSONRPCError(int code, const std::string& message);
167168

169+
/** Get name of RPC authentication cookie file */
170+
boost::filesystem::path GetAuthCookieFile();
171+
/** Generate a new RPC authentication cookie and write it to disk */
172+
bool GenerateAuthCookie(std::string *cookie_out);
173+
/** Read the RPC authentication cookie from disk */
174+
bool GetAuthCookie(std::string *cookie_out);
175+
/** Delete RPC authentication cookie from disk */
176+
void DeleteAuthCookie();
177+
168178
#endif // BITCOIN_RPCPROTOCOL_H

src/rpcserver.cpp

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -597,27 +597,18 @@ void StartRPCThreads()
597597
strAllowed += subnet.ToString() + " ";
598598
LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed);
599599

600-
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
601600
if (mapArgs["-rpcpassword"] == "")
602601
{
603-
unsigned char rand_pwd[32];
604-
GetRandBytes(rand_pwd, 32);
605-
uiInterface.ThreadSafeMessageBox(strprintf(
606-
_("To use bitcoind, or the -server option to bitcoin-qt, you must set an rpcpassword in the configuration file:\n"
607-
"%s\n"
608-
"It is recommended you use the following random password:\n"
609-
"rpcuser=bitcoinrpc\n"
610-
"rpcpassword=%s\n"
611-
"(you do not need to remember this password)\n"
612-
"The username and password MUST NOT be the same.\n"
613-
"If the file does not exist, create it with owner-readable-only file permissions.\n"
614-
"It is also recommended to set alertnotify so you are notified of problems;\n"
615-
"for example: alertnotify=echo %%s | mail -s \"Bitcoin Alert\" [email protected]\n"),
616-
GetConfigFile().string(),
617-
EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32)),
618-
"", CClientUIInterface::MSG_ERROR | CClientUIInterface::SECURE);
619-
StartShutdown();
620-
return;
602+
LogPrintf("No rpcpassword set - using random cookie authentication\n");
603+
if (!GenerateAuthCookie(&strRPCUserColonPass)) {
604+
uiInterface.ThreadSafeMessageBox(
605+
_("Error: A fatal internal error occured, see debug.log for details"), // Same message as AbortNode
606+
"", CClientUIInterface::MSG_ERROR);
607+
StartShutdown();
608+
return;
609+
}
610+
} else {
611+
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
621612
}
622613

623614
assert(rpc_io_service == NULL);
@@ -768,6 +759,8 @@ void StopRPCThreads()
768759
}
769760
deadlineTimers.clear();
770761

762+
DeleteAuthCookie();
763+
771764
rpc_io_service->stop();
772765
g_rpcSignals.Stopped();
773766
if (rpc_worker_group != NULL)

0 commit comments

Comments
 (0)