From 15be27460191a9ffb149cc98f6fbf97c369a6b1e Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 29 Jan 2018 12:57:09 -0500 Subject: [PATCH] Avoid misleading psql password prompt when username is multiply specified. When a password is needed, cases such as psql -d "postgresql://alice@localhost/testdb" -U bob would incorrectly prompt for "Password for user bob: ", when actually the connection will be attempted with username alice. The priority order of which name to use isn't that important here, but the misleading prompt is. When we are prompting for a password after initial connection failure, we can fix this reliably by looking at PQuser(conn) to see how libpq interpreted the connection arguments. But when we're doing a forced password prompt because of a -W switch, we can't use that solution. Fortunately, because the main use of -W is for noninteractive situations, it's less critical to produce a helpful prompt in such cases. I made the startup prompt for -W just say "Password: " all the time, rather than expending extra code on trying to identify which username to use. In the case of a \c command (after -W has been given), there's already logic in do_connect that determines whether the "dbname" is a connstring or URI, so we can avoid lobotomizing the prompt except in cases that are actually dubious. (We could do similarly in startup.c if anyone complains, but for now it seems not worthwhile, especially since that would still be only a partial solution.) Per bug #15025 from Akos Vandra. Although this is arguably a bug fix, it doesn't seem worth back-patching. The case where it matters seems like a very corner-case usage, and someone might complain that we'd changed the behavior of -W in a minor release. Discussion: https://postgr.es/m/20180123130013.7407.24749@wrigleys.postgresql.org --- src/bin/psql/command.c | 17 ++++++++++++++--- src/bin/psql/startup.c | 31 +++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 015c391aa4..3560318749 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2829,7 +2829,7 @@ prompt_for_password(const char *username) { char buf[100]; - if (username == NULL) + if (username == NULL || username[0] == '\0') simple_prompt("Password: ", buf, sizeof(buf), false); else { @@ -2960,7 +2960,14 @@ do_connect(enum trivalue reuse_previous_specification, */ if (pset.getPassword == TRI_YES) { - password = prompt_for_password(user); + /* + * If a connstring or URI is provided, we can't be sure we know which + * username will be used, since we haven't parsed that argument yet. + * Don't risk issuing a misleading prompt. As in startup.c, it does + * not seem worth working harder, since this getPassword option is + * normally only used in noninteractive cases. + */ + password = prompt_for_password(has_connection_string ? NULL : user); } else if (o_conn && keep_password) { @@ -3026,8 +3033,12 @@ do_connect(enum trivalue reuse_previous_specification, */ if (!password && PQconnectionNeedsPassword(n_conn) && pset.getPassword != TRI_NO) { + /* + * Prompt for password using the username we actually connected + * with --- it might've come out of "dbname" rather than "user". + */ + password = prompt_for_password(PQuser(n_conn)); PQfinish(n_conn); - password = prompt_for_password(user); continue; } diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index ec6ae45b24..be57574cd3 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -101,7 +101,6 @@ main(int argc, char *argv[]) int successResult; bool have_password = false; char password[100]; - char *password_prompt = NULL; bool new_pass; set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("psql")); @@ -205,15 +204,14 @@ main(int argc, char *argv[]) pset.popt.topt.recordSep.separator_zero = false; } - if (options.username == NULL) - password_prompt = pg_strdup(_("Password: ")); - else - password_prompt = psprintf(_("Password for user %s: "), - options.username); - if (pset.getPassword == TRI_YES) { - simple_prompt(password_prompt, password, sizeof(password), false); + /* + * We can't be sure yet of the username that will be used, so don't + * offer a potentially wrong one. Typical uses of this option are + * noninteractive anyway. + */ + simple_prompt("Password: ", password, sizeof(password), false); have_password = true; } @@ -252,15 +250,28 @@ main(int argc, char *argv[]) !have_password && pset.getPassword != TRI_NO) { + /* + * Before closing the old PGconn, extract the user name that was + * actually connected with --- it might've come out of a URI or + * connstring "database name" rather than options.username. + */ + const char *realusername = PQuser(pset.db); + char *password_prompt; + + if (realusername && realusername[0]) + password_prompt = psprintf(_("Password for user %s: "), + realusername); + else + password_prompt = pg_strdup(_("Password: ")); PQfinish(pset.db); + simple_prompt(password_prompt, password, sizeof(password), false); + free(password_prompt); have_password = true; new_pass = true; } } while (new_pass); - free(password_prompt); - if (PQstatus(pset.db) == CONNECTION_BAD) { fprintf(stderr, "%s: %s", pset.progname, PQerrorMessage(pset.db));