Add new function, PQchangePassword(), to libpq
authorJoe Conway <mail@joeconway.com>
Tue, 9 Jan 2024 14:16:48 +0000 (09:16 -0500)
committerJoe Conway <mail@joeconway.com>
Tue, 9 Jan 2024 14:16:48 +0000 (09:16 -0500)
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
src/bin/psql/command.c
src/interfaces/libpq/exports.txt
src/interfaces/libpq/fe-auth.c
src/interfaces/libpq/libpq-fe.h

index ed88ac001a17e4970ff7142edf24e33ae5c53e4f..21195e0e728b55a1af1f95d621fb01d365e0ca67 100644 (file)
@@ -7116,6 +7116,45 @@ char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
     </listitem>
    </varlistentry>
 
+   <varlistentry id="libpq-PQchangePassword">
+    <term><function>PQchangePassword</function><indexterm><primary>PQchangePassword</primary></indexterm></term>
+
+    <listitem>
+     <para>
+      Changes a <productname>PostgreSQL</productname> password.
+<synopsis>
+PGresult *PQchangePassword(PGconn *conn, const char *user, const char *passwd);
+</synopsis>
+      This function uses <function>PQencryptPasswordConn</function>
+      to build and execute the command <literal>ALTER USER ... PASSWORD
+      '...'</literal>, thereby changing the user's password. It exists for
+      the same reason as <function>PQencryptPasswordConn</function>, but
+      is more convenient as it both builds and runs the command for you.
+      <xref linkend="libpq-PQencryptPasswordConn"/> is passed a
+      <symbol>NULL</symbol> for the algorithm argument, hence encryption is
+      done according to the server's <xref linkend="guc-password-encryption"/>
+      setting.
+     </para>
+
+     <para>
+      The <parameter>user</parameter> and <parameter>passwd</parameter> arguments
+      are the SQL name of the target user, and the new cleartext password.
+     </para>
+
+     <para>
+      Returns a <structname>PGresult</structname> pointer representing
+      the result of the <literal>ALTER USER</literal> command, or
+      a null pointer if the routine failed before issuing any command.
+      The <xref linkend="libpq-PQresultStatus"/> 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
+      <symbol>PGRES_FATAL_ERROR</symbol>). Use
+      <xref linkend="libpq-PQerrorMessage"/> to get more information about
+      such errors.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="libpq-PQencryptPassword">
     <term><function>PQencryptPassword</function><indexterm><primary>PQencryptPassword</primary></indexterm></term>
 
index 9103bc346576415f9e2d76df03d4526f1ec69771..eb216b7c09efb45bf9812b22121496ab284dc290 100644 (file)
@@ -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);
index 850734ac96c5ca79a98a247b6f1403d9e6c9d67c..28b861fd93bcfcb26523ee6502fa970a9306d9b6 100644 (file)
@@ -191,3 +191,4 @@ PQclosePrepared           188
 PQclosePortal             189
 PQsendClosePrepared       190
 PQsendClosePortal         191
+PQchangePassword          192
index da5ccd324d9a721be4ea1fc1305d90717300e1c7..1a8e4f6fbfa354af3cccc537d8caeee45ab5b9e7 100644 (file)
@@ -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;
+                       }
+               }
+       }
+}
index 12eb72c1fef5dbfbd7d3917a173b54541b746b30..f0ec660cb69ff1fd8797ec7afff6301f0f4e3eb9 100644 (file)
@@ -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 === */