From a7be2a6c262d5352756d909b29c419ea5e5fa1d9 Mon Sep 17 00:00:00 2001 From: Joe Conway Date: Tue, 9 Jan 2024 09:16:48 -0500 Subject: [PATCH] Add new function, PQchangePassword(), to libpq Essentially this moves the non-interactive part of psql's "\password" command into an exported client function. The password is not sent to the server in cleartext because it is "encrypted" (in the case of scram and md5 it is actually hashed, but we have called these encrypted passwords for a long time now) on the client side. This is good because it ensures the cleartext password is never known by the server, and therefore won't end up in logs, pg_stat displays, etc. In other words, it exists for the same reason as PQencryptPasswordConn(), but is more convenient as it both builds and runs the "ALTER USER" command for you. PQchangePassword() uses PQencryptPasswordConn() to do the password encryption. PQencryptPasswordConn() is passed a NULL for the algorithm argument, hence encryption is done according to the server's password_encryption setting. Also modify the psql client to use the new function. That provides a builtin test case. Ultimately drivers built on top of libpq should expose this function and its use should be generally encouraged over doing ALTER USER directly for password changes. Author: Joe Conway Reviewed-by: Tom Lane Discussion: https://postgr.es/m/flat/b75955f7-e8cc-4bbd-817f-ef536bacbe93%40joeconway.com --- doc/src/sgml/libpq.sgml | 39 +++++++++++++++ src/bin/psql/command.c | 20 ++------ src/interfaces/libpq/exports.txt | 1 + src/interfaces/libpq/fe-auth.c | 81 ++++++++++++++++++++++++++++++++ src/interfaces/libpq/libpq-fe.h | 1 + 5 files changed, 125 insertions(+), 17 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index ed88ac001a..21195e0e72 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -7116,6 +7116,45 @@ char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, + + PQchangePasswordPQchangePassword + + + + Changes a PostgreSQL password. + +PGresult *PQchangePassword(PGconn *conn, const char *user, const char *passwd); + + This function uses PQencryptPasswordConn + to build and execute the command ALTER USER ... PASSWORD + '...', thereby changing the user's password. It exists for + the same reason as PQencryptPasswordConn, but + is more convenient as it both builds and runs the command for you. + is passed a + NULL for the algorithm argument, hence encryption is + done according to the server's + setting. + + + + The user and passwd arguments + are the SQL name of the target user, and the new cleartext password. + + + + Returns a PGresult pointer representing + the result of the ALTER USER command, or + a null pointer if the routine failed before issuing any command. + The function should be called + to check the return value for any errors (including the value of a null + pointer, in which case it will return + PGRES_FATAL_ERROR). Use + to get more information about + such errors. + + + + PQencryptPasswordPQencryptPassword diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 9103bc3465..eb216b7c09 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2158,29 +2158,15 @@ exec_command_password(PsqlScanState scan_state, bool active_branch) } else { - char *encrypted_password; + PGresult *res = PQchangePassword(pset.db, user, pw1); - encrypted_password = PQencryptPasswordConn(pset.db, pw1, user, NULL); - - if (!encrypted_password) + if (PQresultStatus(res) != PGRES_COMMAND_OK) { pg_log_info("%s", PQerrorMessage(pset.db)); success = false; } - else - { - PGresult *res; - printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD ", - fmtId(user)); - appendStringLiteralConn(&buf, encrypted_password, pset.db); - res = PSQLexec(buf.data); - if (!res) - success = false; - else - PQclear(res); - PQfreemem(encrypted_password); - } + PQclear(res); } free(user); diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 850734ac96..28b861fd93 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -191,3 +191,4 @@ PQclosePrepared 188 PQclosePortal 189 PQsendClosePrepared 190 PQsendClosePortal 191 +PQchangePassword 192 diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index da5ccd324d..1a8e4f6fbf 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -1372,3 +1372,84 @@ PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, return crypt_pwd; } + +/* + * PQchangePassword -- exported routine to change a password + * + * This is intended to be used by client applications that wish to + * change the password for a user. The password is not sent in + * cleartext because it is encrypted on the client side. This is + * good because it ensures the cleartext password is never known by + * the server, and therefore won't end up in logs, pg_stat displays, + * etc. The password encryption is performed by PQencryptPasswordConn(), + * which is passed a NULL for the algorithm argument. Hence encryption + * is done according to the server's password_encryption + * setting. We export the function so that clients won't be dependent + * on the implementation specific details with respect to how the + * server changes passwords. + * + * Arguments are a connection object, the SQL name of the target user, + * and the cleartext password. + * + * Return value is the PGresult of the executed ALTER USER statement + * or NULL if we never get there. The caller is responsible to PQclear() + * the returned PGresult. + * + * PQresultStatus() should be called to check the return value for errors, + * and PQerrorMessage() used to get more information about such errors. + */ +PGresult * +PQchangePassword(PGconn *conn, const char *user, const char *passwd) +{ + char *encrypted_password = PQencryptPasswordConn(conn, passwd, + user, NULL); + + if (!encrypted_password) + { + /* PQencryptPasswordConn() already registered the error */ + return NULL; + } + else + { + char *fmtpw = PQescapeLiteral(conn, encrypted_password, + strlen(encrypted_password)); + + /* no longer needed, so clean up now */ + PQfreemem(encrypted_password); + + if (!fmtpw) + { + /* PQescapeLiteral() already registered the error */ + return NULL; + } + else + { + char *fmtuser = PQescapeIdentifier(conn, user, strlen(user)); + + if (!fmtuser) + { + /* PQescapeIdentifier() already registered the error */ + PQfreemem(fmtpw); + return NULL; + } + else + { + PQExpBufferData buf; + PGresult *res; + + initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD %s", + fmtuser, fmtpw); + + res = PQexec(conn, buf.data); + + /* clean up */ + termPQExpBuffer(&buf); + PQfreemem(fmtuser); + PQfreemem(fmtpw); + + return res; + } + } + } +} diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 12eb72c1fe..f0ec660cb6 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -659,6 +659,7 @@ extern int PQenv2encoding(void); extern char *PQencryptPassword(const char *passwd, const char *user); extern char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, const char *algorithm); +extern PGresult *PQchangePassword(PGconn *conn, const char *user, const char *passwd); /* === in encnames.c === */ -- 2.39.5