From 7a43398c90ac9ed2471879762dc660a9f23d700d Mon Sep 17 00:00:00 2001
From: Jacob Champion <jacob.champion@enterprisedb.com>
Date: Fri, 6 Mar 2026 14:18:49 -0800
Subject: [PATCH v7 4/5] libpq: Allow developers to reimplement libpq-oauth

For PG19, since we won't have the ability to officially switch out flow
plugins, relax the flow-loading code to not require the internal init
function. Modules that don't have one will be treated as custom user
flows in error messages.

This will let bleeding-edge developers more easily test out the API and
provide feedback for PG20, by telling the runtime linker to find a
different libpq-oauth. It remains undocumented for end users.

Discussion: https://postgr.es/m/CAOYmi%2BmrGg%2Bn_X2MOLgeWcj3v_M00gR8uz_D7mM8z%3DdX1JYVbg%40mail.gmail.com
---
 src/interfaces/libpq/fe-auth-oauth.h |  2 +-
 src/interfaces/libpq/fe-auth-oauth.c | 71 ++++++++++++++++------------
 2 files changed, 42 insertions(+), 31 deletions(-)

diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 2b22d23ffde..872f5df551a 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -36,7 +36,7 @@ typedef struct
 
 	bool		v1;
 	bool		builtin;
-	void	   *builtin_flow;
+	void	   *flow_module;
 } fe_oauth_state;
 
 extern void pqClearOAuthToken(PGconn *conn);
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index 40800183eb9..dff00dfc6c9 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -889,8 +889,8 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *re
 		"libpq-oauth" DLSUFFIX;
 #endif
 
-	state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
-	if (!state->builtin_flow)
+	state->flow_module = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
+	if (!state->flow_module)
 	{
 		/*
 		 * For end users, this probably isn't an error condition, it just
@@ -905,8 +905,16 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *re
 		return 0;
 	}
 
-	if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
-		|| (start_flow = dlsym(state->builtin_flow, "pg_start_oauthbearer")) == NULL)
+	/*
+	 * Our libpq-oauth.so provides a special initialization function for libpq
+	 * integration. If we don't find this, assume that a custom module is in
+	 * use instead.
+	 */
+	init = dlsym(state->flow_module, "libpq_oauth_init");
+	if (!init)
+		state->builtin = false; /* adjust our error messages */
+
+	if ((start_flow = dlsym(state->flow_module, "pg_start_oauthbearer")) == NULL)
 	{
 		/*
 		 * This is more of an error condition than the one above, but the
@@ -916,8 +924,8 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *re
 		if (oauth_unsafe_debugging_enabled())
 			fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
 
-		dlclose(state->builtin_flow);
-		state->builtin_flow = NULL;
+		dlclose(state->flow_module);
+		state->flow_module = NULL;
 
 		request->error = libpq_gettext("could not find entry point for libpq-oauth");
 		return -1;
@@ -928,39 +936,42 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *re
 	 * permanently.
 	 */
 
-	/*
-	 * We need to inject necessary function pointers into the module. This
-	 * only needs to be done once -- even if the pointers are constant,
-	 * assigning them while another thread is executing the flows feels like
-	 * tempting fate.
-	 */
-	if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
+	if (init)
 	{
-		/* Should not happen... but don't continue if it does. */
-		Assert(false);
+		/*
+		 * We need to inject necessary function pointers into the module. This
+		 * only needs to be done once -- even if the pointers are constant,
+		 * assigning them while another thread is executing the flows feels
+		 * like tempting fate.
+		 */
+		if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
+		{
+			/* Should not happen... but don't continue if it does. */
+			Assert(false);
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  "use_builtin_flow: failed to lock mutex (%d)\n",
-						  lockerr);
+			appendPQExpBuffer(&conn->errorMessage,
+							  "use_builtin_flow: failed to lock mutex (%d)\n",
+							  lockerr);
 
-		request->error = "";	/* satisfy report_flow_error() */
-		return -1;
-	}
+			request->error = "";	/* satisfy report_flow_error() */
+			return -1;
+		}
 
-	if (!initialized)
-	{
-		init(
+		if (!initialized)
+		{
+			init(
 #ifdef ENABLE_NLS
-			 libpq_gettext
+				 libpq_gettext
 #else
-			 NULL
+				 NULL
 #endif
-			);
+				);
 
-		initialized = true;
-	}
+			initialized = true;
+		}
 
-	pthread_mutex_unlock(&init_mutex);
+		pthread_mutex_unlock(&init_mutex);
+	}
 
 	return (start_flow(conn, request) == 0) ? 1 : -1;
 }
-- 
2.34.1

