From a2e101cb97446de21a8ddf5231a4ccc795756cc7 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <lic@highgo.com>
Date: Wed, 10 Jun 2026 13:55:40 +0800
Subject: [PATCH v1] Fix md5_password_warnings for role and database settings

MD5 authentication warnings are queued during authentication, before
startup options and role/database settings have been applied. The code
checked md5_password_warnings at queue time, so settings such as
ALTER ROLE ... SET md5_password_warnings = off did not suppress the
warning, even though the established session showed the GUC as off.

Keep the connection-warning infrastructure generic by allowing each
queued warning to carry an optional filter callback. Evaluate that
callback when warnings are emitted, after startup options and
role/database settings have been processed.

Use this for MD5 authentication warnings, while leaving password
expiration warnings unchanged. Add test coverage for an MD5-authenticated
role with md5_password_warnings disabled.

Author: Chao Li <lic@highgo.com>
---
 src/backend/libpq/crypt.c                 | 31 ++++++++------
 src/backend/utils/init/postinit.c         | 52 +++++++++++++++--------
 src/include/miscadmin.h                   |  4 +-
 src/test/authentication/t/001_password.pl | 20 +++++++++
 4 files changed, 76 insertions(+), 31 deletions(-)

diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 739a2e6fa8b..28857773d9c 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -32,6 +32,8 @@ int			password_expiration_warning_threshold = 604800;
 /* Enables deprecation warnings for MD5 passwords. */
 bool		md5_password_warnings = true;
 
+static bool md5_password_warning_enabled(void);
+
 /*
  * Fetch stored password for a user, for authentication.
  *
@@ -137,7 +139,7 @@ get_role_password(const char *role, const char **logdetail)
 				detail = psprintf(_("The password for role \"%s\" will expire in less than 1 minute."),
 								  role);
 
-			StoreConnectionWarning(warning, detail);
+			StoreConnectionWarning(warning, detail, NULL);
 
 			MemoryContextSwitchTo(oldcontext);
 		}
@@ -296,22 +298,19 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
 	if (strlen(client_pass) == strlen(crypt_pwd) &&
 		timingsafe_bcmp(client_pass, crypt_pwd, strlen(crypt_pwd)) == 0)
 	{
+		MemoryContext oldcontext;
+		char	   *warning;
+		char	   *detail;
+
 		retval = STATUS_OK;
 
-		if (md5_password_warnings)
-		{
-			MemoryContext oldcontext;
-			char	   *warning;
-			char	   *detail;
+		oldcontext = MemoryContextSwitchTo(TopMemoryContext);
 
-			oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+		warning = pstrdup(_("authenticated with an MD5-encrypted password"));
+		detail = pstrdup(_("MD5 password support is deprecated and will be removed in a future release of PostgreSQL."));
+		StoreConnectionWarning(warning, detail, md5_password_warning_enabled);
 
-			warning = pstrdup(_("authenticated with an MD5-encrypted password"));
-			detail = pstrdup(_("MD5 password support is deprecated and will be removed in a future release of PostgreSQL."));
-			StoreConnectionWarning(warning, detail);
-
-			MemoryContextSwitchTo(oldcontext);
-		}
+		MemoryContextSwitchTo(oldcontext);
 	}
 	else
 	{
@@ -323,6 +322,12 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
 	return retval;
 }
 
+static bool
+md5_password_warning_enabled(void)
+{
+	return md5_password_warnings;
+}
+
 /*
  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
  *
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index c1457eb34f0..a51a2f119d0 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -74,9 +74,15 @@
 /* has this backend called EmitConnectionWarnings()? */
 static bool ConnectionWarningsEmitted;
 
-/* content of warnings to send via EmitConnectionWarnings() */
-static List *ConnectionWarningMessages;
-static List *ConnectionWarningDetails;
+typedef struct ConnectionWarning
+{
+	char	   *message;
+	char	   *detail;
+	ConnectionWarningFilter filter;
+}			ConnectionWarning;
+
+/* warnings to send via EmitConnectionWarnings() */
+static List *ConnectionWarnings;
 
 static HeapTuple GetDatabaseTuple(const char *dbname);
 static HeapTuple GetDatabaseTupleByOid(Oid dboid);
@@ -1499,15 +1505,19 @@ ThereIsAtLeastOneRole(void)
 
 /*
  * Stores a warning message to be sent later via EmitConnectionWarnings().
- * Both msg and detail must be non-NULL.
+ * Both msg and detail must be non-NULL.  If filter is non-NULL, it is called
+ * just before the warning is emitted, after startup and role/database settings
+ * have been applied.
  *
  * NB: Caller should ensure the strings are allocated in a long-lived context
- * like TopMemoryContext.
+ * like TopMemoryContext.  This function takes ownership of the strings, which
+ * will be freed in EmitConnectionWarnings().
  */
 void
-StoreConnectionWarning(char *msg, char *detail)
+StoreConnectionWarning(char *msg, char *detail, ConnectionWarningFilter filter)
 {
 	MemoryContext oldcontext;
+	ConnectionWarning *warning;
 
 	Assert(msg);
 	Assert(detail);
@@ -1517,8 +1527,11 @@ StoreConnectionWarning(char *msg, char *detail)
 
 	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
 
-	ConnectionWarningMessages = lappend(ConnectionWarningMessages, msg);
-	ConnectionWarningDetails = lappend(ConnectionWarningDetails, detail);
+	warning = palloc(sizeof(ConnectionWarning));
+	warning->message = msg;
+	warning->detail = detail;
+	warning->filter = filter;
+	ConnectionWarnings = lappend(ConnectionWarnings, warning);
 
 	MemoryContextSwitchTo(oldcontext);
 }
@@ -1532,22 +1545,27 @@ StoreConnectionWarning(char *msg, char *detail)
 static void
 EmitConnectionWarnings(void)
 {
-	ListCell   *lc_msg;
-	ListCell   *lc_detail;
+	ListCell   *lc;
 
 	if (ConnectionWarningsEmitted)
 		elog(ERROR, "EmitConnectionWarnings() called more than once");
 	else
 		ConnectionWarningsEmitted = true;
 
-	forboth(lc_msg, ConnectionWarningMessages,
-			lc_detail, ConnectionWarningDetails)
+	foreach(lc, ConnectionWarnings)
 	{
-		ereport(WARNING,
-				(errmsg("%s", (char *) lfirst(lc_msg)),
-				 errdetail("%s", (char *) lfirst(lc_detail))));
+		ConnectionWarning *warning = lfirst(lc);
+
+		if (warning->filter == NULL || warning->filter())
+			ereport(WARNING,
+					(errmsg("%s", warning->message),
+					 errdetail("%s", warning->detail)));
+
+		pfree(warning->message);
+		pfree(warning->detail);
+		pfree(warning);
 	}
 
-	list_free_deep(ConnectionWarningMessages);
-	list_free_deep(ConnectionWarningDetails);
+	list_free(ConnectionWarnings);
+	ConnectionWarnings = NIL;
 }
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 7de0a115402..7170a4bff98 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -516,7 +516,9 @@ extern void InitPostgres(const char *in_dbname, Oid dboid,
 						 uint32 flags,
 						 char *out_dbname);
 extern void BaseInit(void);
-extern void StoreConnectionWarning(char *msg, char *detail);
+typedef bool (*ConnectionWarningFilter) (void);
+extern void StoreConnectionWarning(char *msg, char *detail,
+								   ConnectionWarningFilter filter);
 
 /* in utils/init/miscinit.c */
 extern PGDLLIMPORT bool IgnoreSystemIndexes;
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 69ed4919b16..cb98ef840ab 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -157,6 +157,14 @@ is( $node->psql(
 	),
 	$md5_works ? 0 : 3,
 	'created user with md5 password');
+is( $node->psql(
+		'postgres',
+		"SET password_encryption='md5';
+		 CREATE ROLE md5_role_no_warnings LOGIN PASSWORD 'pass';
+		 ALTER ROLE md5_role_no_warnings SET md5_password_warnings = off;"
+	),
+	$md5_works ? 0 : 3,
+	'created user with md5 password and MD5 warnings disabled');
 # Set up a table for tests of SYSTEM_USER.
 $node->safe_psql(
 	'postgres',
@@ -504,6 +512,18 @@ SKIP:
 		expected_stderr => qr/authenticated with an MD5-encrypted password/,
 		log_like =>
 		  [qr/connection authenticated: identity="md5_role" method=md5/]);
+
+	my ($ret, $stdout, $stderr) = $node->psql(
+		'postgres',
+		'SELECT 1',
+		connstr => 'user=md5_role_no_warnings',
+		extra_params => ['-w'],
+		on_error_stop => 0);
+	is($ret, 0, 'md5 with warnings disabled');
+	unlike(
+		$stderr,
+		qr/authenticated with an MD5-encrypted password/,
+		'md5 with warnings disabled: no MD5 authentication warning');
 }
 
 # require_auth succeeds with SCRAM required.
-- 
2.50.1 (Apple Git-155)

