This PR
- adds
-stdinwalletpassphrasefor use withwalletpasshprase(change) - adds no-echo for passwords (
-stdinrpcpassand above)
It may not be ideal, but it's better than having to clear the screen whenever you unlock the wallet.
This PR
-stdinwalletpassphrase for use with walletpasshprase(change)-stdinrpcpass and above)It may not be ideal, but it's better than having to clear the screen whenever you unlock the wallet.
Concept ACK
<!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--174a7506f384e20aa4161008e828411d-->
No conflicts as of last run.
77 | @@ -78,6 +78,14 @@ 78 | #include <openssl/conf.h> 79 | #include <thread> 80 | 81 | +#ifdef WIN32
As this is only used in bitcoin-cli, it doesn't need to be in util.cpp (which is shared between the server and client), would be better in a client-specific utility, e.g. compat/noecho.{cpp,h} or something like that, that is only linked into -cli.
Good point. Fixing.
77 | @@ -78,6 +78,14 @@ 78 | #include <openssl/conf.h> 79 | #include <thread> 80 | 81 | +#ifdef WIN32 82 | +#include <windows.h> // for SetStdinEcho() 83 | +#else 84 | +#include <termios.h> // for SetStdinEcho()
is this generally supported on all UNIX, or does it need checks in the build system?
termios.h? It seems to be pretty standard FWICT.
going to test on FreeBSD and OpenBSD
Concept ACK
415 | @@ -414,14 +416,33 @@ static int CommandLineRPC(int argc, char *argv[]) 416 | argc--; 417 | argv++; 418 | } 419 | + SetStdinEcho(false);
-stdin processing-stdinrpcpass or -stdinwalletpassphrase is called to avoid interfering with scripts that pipe in data, not attached to a TTYGood points. Addressed both.
Checked that this compiles and works as-is on OpenBSD 6.3 and FreeBSD 11.2-RELEASE-p2.
Would be good to have functional tests for this, in as far as that is possibleāseems tricky to simulate a terminal
Is it even possible to test stdin echo?
Yes, it's certainly possible. However I'm afraid that it'd involve allocating a pty and emulating an actual terminal, all from python. There is code for that, but it's probably a bit too much to pull in a dependency for. It's a good indication that we should stop here at accommodating terminals, though, at the minimum possible code dedicated to this (which is easy to manually test.)
429 | gArgs.ForceSetArg("-rpcpassword", rpcPass);
430 | + SetStdinEcho(true);
431 | }
432 | std::vector<std::string> args = std::vector<std::string>(&argv[1], &argv[argc]);
433 | + if (gArgs.GetBoolArg("-stdinwalletpassphrase", false)) {
434 | + SetStdinEcho(false);
will this leave the user's terminal in a bad state in case of an exception?
if so, might want to use a RAII class
It didn't for Macs, but you're right, there's really no guarantees so a RAII makes sense. Will fix.
Added RAII wrapper.
lightly-tested ACK 2836f6fc1ef98cef41993970f8b3f7c0758484f5
From the control flow, it seems a bit odd that one gets asked for the password first via std-in, but then get an error like RPC password> error: too few parameters (need at least command).
IMO a) we should newline after a password read and b) eventually check for missing command (and other static checks) before asking for the password.
@jonasschnelli That particular case will never happen, as the code already checks that the command starts with "walletpassphrase". Are there other cases or potential cases?
21 | +{ 22 | +#ifdef WIN32 23 | + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); 24 | + DWORD mode; 25 | + GetConsoleMode(hStdin, &mode); 26 | + if (!enable) mode &= ~ENABLE_ECHO_INPUT; else mode |= ENABLE_ECHO_INPUT;
2018-09-22 21:56:54 cpplint(pr=13716): src/compat/stdin.cpp:26: Else clause should never be on same line as else (use 2 lines) [whitespace/newline] [4]
2018-09-22 21:56:54 cpplint(pr=13716): src/compat/stdin.cpp:26: If/else bodies with multiple statements require braces [readability/braces] [4]
26 | + if (!enable) mode &= ~ENABLE_ECHO_INPUT; else mode |= ENABLE_ECHO_INPUT; 27 | + SetConsoleMode(hStdin, mode); 28 | +#else 29 | + struct termios tty; 30 | + tcgetattr(STDIN_FILENO, &tty); 31 | + if (!enable) tty.c_lflag &= ~ECHO; else tty.c_lflag |= ECHO;
2018-09-22 21:56:54 cpplint(pr=13716): src/compat/stdin.cpp:31: Else clause should never be on same line as else (use 2 lines) [whitespace/newline] [4]
2018-09-22 21:56:54 cpplint(pr=13716): src/compat/stdin.cpp:31: If/else bodies with multiple statements require braces [readability/braces] [4]
A negative integer is implicitly converted to unsigned type here. Please make the conversion explicit.
Is there a case when this would be a problem?
I may have misunderstood what you were saying here; what are you suggesting I do, exactly?
430 | } 431 | std::vector<std::string> args = std::vector<std::string>(&argv[1], &argv[argc]); 432 | + if (gArgs.GetBoolArg("-stdinwalletpassphrase", false)) { 433 | + NO_STDIN_ECHO(); 434 | + std::string walletPass; 435 | + if (args.size() < 1 || args[0].substr(0, strlen("walletpassphrase")) != "walletpassphrase") {
Nit: Could be written with something more idiomatic than strlen? :-)
Fixed. :P
Addressed @practicalswift nits.
tACK b197e57 for macOS 10.14.3
Nit: return \n after password entry.
Needs Windows testing too, @ken2812221?
For a followup it would be great if all wallet RPC's that need a private key worked directly with -stdinwalletpassphrase and just lock when they're done (though then the question is what default timeout you need).
@kallewoof it didn't return a new line for me (macOS 10.14.3): <img width="665" alt="schermafbeelding 2019-02-13 om 10 11 12" src="https://user-images.githubusercontent.com/10217/52700180-c1c46c80-2f77-11e9-8975-da11c9d0cc18.png">
<img width="560" alt="schermafbeelding 2019-02-13 om 10 17 12" src="https://user-images.githubusercontent.com/10217/52700569-8bd3b800-2f78-11e9-8449-cc1c4b44e1ed.png">
re-tACK 110e821
Oh, okay! So this is the case for pre-existing -stdinrpcpass as well. Fixed in d1688c9.
re-tACK d1688c9
412 | @@ -411,18 +413,41 @@ static int CommandLineRPC(int argc, char *argv[]) 413 | } 414 | std::string rpcPass; 415 | if (gArgs.GetBoolArg("-stdinrpcpass", false)) { 416 | + NO_STDIN_ECHO(); 417 | + if (!StdinReady()) { 418 | + fprintf(stderr, "RPC password> ");
you could use fputs here (same below) and avoid introducing the locale dependency exception
tfm::format(std::cerr, ... should work as well
D'oh, yeah. Removing linter changes and using fputs.
419 | + fflush(stderr); 420 | + } 421 | if (!std::getline(std::cin, rpcPass)) { 422 | throw std::runtime_error("-stdinrpcpass specified but failed to read from standard input"); 423 | } 424 | + fputc('\n', stdout);
please only print this extra newline if stdin terminal is detected, and not when, say, piping in from a script (same below 2x)
Understood. I think the latest version achieves this. (See StdinTerminal().)
$ ./bitcoin-cli -datadir=d -regtest encryptwallet foobar38
wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.
$ ./bitcoin-cli -datadir=d -regtest -stdinwalletpassphrase walletpassphrase 10
Wallet passphrase>
$ echo foobar38 | ./bitcoin-cli -datadir=d -regtest -stdinwalletpassphrase walletpassphrase 10
$ echo xfoobar38 | ./bitcoin-cli -datadir=d -regtest -stdinwalletpassphrase walletpassphrase 10
error code: -14
error message:
Error: The wallet passphrase entered was incorrect.
Thanks, LGTM now code review ACK 50c4afa3c420f11329cffb091b62beeb96b39183