On 1/6/24 15:10, Tom Lane wrote:
Joe Conway <m...@joeconway.com> writes:
The only code specific comments were Tom's above, which have been addressed. If there are no serious objections I plan to commit this relatively soon.

I had not actually read this patchset before, but now I have, and
I have a few minor suggestions:

Many thanks!

* The API comment for PQchangePassword should specify that encryption
is done according to the server's password_encryption setting, and
probably the SGML docs should too.  You shouldn't have to read the
code to discover that.

Check

* I don't especially care for the useless initializations of
encrypted_password, fmtpw, and fmtuser.  In all those cases the
initial NULL is immediately replaced by a valid value.  Probably
the compiler will figure out that the initializations are useless,
but human readers have to do so as well.  Moreover, I think this
style is more bug-prone not less so, because if you ever change
the logic in a way that causes some code paths to fail to set
the variables, you won't get use-of-possibly-uninitialized-value
warnings from the compiler.

* Perhaps move the declaration of "buf" to the inner block where
it's actually used?

Makes sense -- fixed

* This could be shortened to just "return res":

+                               if (!res)
+                                       return NULL;
+                               else
+                                       return res;

Heh, apparently I needed more coffee at this point :-)

* I'd make the SGML documentation a bit more explicit about the
return value, say

+       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.

Fixed.

I also ran pgindent. I was kind of surprised/unhappy when it made me change this (which aligned the two var names):
8<------------
<tab><tab><tab><tab>PQExpBufferData<tab>buf;
<tab><tab><tab><tab>PGresult<tab><sp><sp><sp>*res;
8<------------

to this (which leaves the var names unaligned):
8<------------
<tab><tab><tab><tab>PQExpBufferData<sp>buf;
<tab><tab><tab><tab>PGresult<sp><sp><sp>*res;
8<------------

Anyway, the resulting adjustments attached.

--
Joe Conway
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 850734a..28b861f 100644
*** a/src/interfaces/libpq/exports.txt
--- b/src/interfaces/libpq/exports.txt
*************** PQclosePrepared           188
*** 191,193 ****
--- 191,194 ----
  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 912aa14..dcb7c9f 100644
*** a/src/interfaces/libpq/fe-auth.c
--- b/src/interfaces/libpq/fe-auth.c
*************** PQencryptPasswordConn(PGconn *conn, cons
*** 1372,1374 ****
--- 1372,1455 ----
  
  	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 97762d5..bc8e6de 100644
*** a/src/interfaces/libpq/libpq-fe.h
--- b/src/interfaces/libpq/libpq-fe.h
*************** extern int	PQenv2encoding(void);
*** 659,664 ****
--- 659,665 ----
  
  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 === */
  
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 82cc091..81402e0 100644
*** a/src/bin/psql/command.c
--- b/src/bin/psql/command.c
*************** exec_command_password(PsqlScanState scan
*** 2158,2186 ****
  		}
  		else
  		{
! 			char	   *encrypted_password;
! 
! 			encrypted_password = PQencryptPasswordConn(pset.db, pw1, user, NULL);
  
! 			if (!encrypted_password)
  			{
  				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);
! 			}
  		}
  
  		free(user);
--- 2158,2172 ----
  		}
  		else
  		{
! 			PGresult   *res = PQchangePassword(pset.db, user, pw1);
  
! 			if (PQresultStatus(res) != PGRES_COMMAND_OK)
  			{
  				pg_log_info("%s", PQerrorMessage(pset.db));
  				success = false;
  			}
  
! 			PQclear(res);
  		}
  
  		free(user);
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index ed88ac0..983027b 100644
*** a/doc/src/sgml/libpq.sgml
--- b/doc/src/sgml/libpq.sgml
*************** char *PQencryptPasswordConn(PGconn *conn
*** 7116,7121 ****
--- 7116,7159 ----
      </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 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>
  

Reply via email to