From 20b4fbe435d31c4e784ce56c887a8d5c365d8ea5 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jacob.champion@enterprisedb.com>
Date: Wed, 26 Mar 2025 10:55:28 -0700
Subject: [PATCH v2] WIP: split Device Authorization flow into dlopen'd module

See notes on mailing list.

Co-authored-by: Daniel Gustafsson <daniel@yesql.se>
---
 meson.build                                   |  12 +-
 src/interfaces/Makefile                       |   9 +
 src/interfaces/libpq-oauth/Makefile           |  56 ++++++
 src/interfaces/libpq-oauth/README             |  18 ++
 src/interfaces/libpq-oauth/exports.txt        |   4 +
 src/interfaces/libpq-oauth/meson.build        |  32 +++
 src/interfaces/libpq-oauth/nls.mk             |  15 ++
 .../oauth-curl.c}                             |   9 +-
 src/interfaces/libpq-oauth/oauth-curl.h       |  23 +++
 src/interfaces/libpq-oauth/oauth-utils.c      | 190 ++++++++++++++++++
 src/interfaces/libpq-oauth/oauth-utils.h      |  22 ++
 src/interfaces/libpq-oauth/po/LINGUAS         |   0
 src/interfaces/libpq-oauth/po/meson.build     |   3 +
 src/interfaces/libpq/Makefile                 |   4 -
 src/interfaces/libpq/exports.txt              |   1 +
 src/interfaces/libpq/fe-auth-oauth.c          |  52 ++++-
 src/interfaces/libpq/fe-auth-oauth.h          |   4 +-
 src/interfaces/libpq/meson.build              |   4 -
 18 files changed, 431 insertions(+), 27 deletions(-)
 create mode 100644 src/interfaces/libpq-oauth/Makefile
 create mode 100644 src/interfaces/libpq-oauth/README
 create mode 100644 src/interfaces/libpq-oauth/exports.txt
 create mode 100644 src/interfaces/libpq-oauth/meson.build
 create mode 100644 src/interfaces/libpq-oauth/nls.mk
 rename src/interfaces/{libpq/fe-auth-oauth-curl.c => libpq-oauth/oauth-curl.c} (99%)
 create mode 100644 src/interfaces/libpq-oauth/oauth-curl.h
 create mode 100644 src/interfaces/libpq-oauth/oauth-utils.c
 create mode 100644 src/interfaces/libpq-oauth/oauth-utils.h
 create mode 100644 src/interfaces/libpq-oauth/po/LINGUAS
 create mode 100644 src/interfaces/libpq-oauth/po/meson.build

diff --git a/meson.build b/meson.build
index 454ed81f5ea..5620d959056 100644
--- a/meson.build
+++ b/meson.build
@@ -107,6 +107,7 @@ os_deps = []
 backend_both_deps = []
 backend_deps = []
 libpq_deps = []
+libpq_oauth_deps = []
 
 pg_sysroot = ''
 
@@ -3215,17 +3216,18 @@ libpq_deps += [
 
   gssapi,
   ldap_r,
-  # XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-  # during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-  # dependency on that platform?
-  libcurl,
   libintl,
   ssl,
 ]
 
+libpq_oauth_deps += [
+  libcurl,
+]
+
 subdir('src/interfaces/libpq')
-# fe_utils depends on libpq
+# fe_utils and libpq-oauth depends on libpq
 subdir('src/fe_utils')
+subdir('src/interfaces/libpq-oauth')
 
 # for frontend binaries
 frontend_code = declare_dependency(
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index 7d56b29d28f..322a498823d 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,16 @@ include $(top_builddir)/src/Makefile.global
 
 SUBDIRS = libpq ecpg
 
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+endif
+
 $(recurse)
 
 all-ecpg-recurse: all-libpq-recurse
 install-ecpg-recurse: install-libpq-recurse
+
+ifeq ($(with_libcurl), yes)
+all-libpq-oauth-recurse: all-libpq-recurse
+install-libpq-oauth-recurse: install-libpq-recurse
+endif
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
new file mode 100644
index 00000000000..461c44b59c1
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,56 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for libpq-oauth
+#
+# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/interfaces/libpq-oauth/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/interfaces/libpq-oauth
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "libpq-oauth - device authorization oauth support"
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION. (We still put the major version into the name, to make it
+# obvious where the library belongs.)
+NAME = libpq-oauth-$(MAJORVERSION)
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(CPPFLAGS)
+
+OBJS = \
+	$(WIN32RES) \
+	oauth-curl.o \
+	oauth-utils.o
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = -lcurl
+SHLIB_PREREQS = submake-libpq
+
+SHLIB_EXPORTS = exports.txt
+
+PKG_CONFIG_REQUIRES_PRIVATE = libpq
+#
+# Make dependencies on pg_config_paths.h visible in all builds.
+oauth-curl.o: oauth-curl.c $(top_builddir)/src/port/pg_config_paths.h
+
+$(top_builddir)/src/port/pg_config_paths.h:
+	$(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
+
+all: all-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+install: all installdirs install-lib
+
+installdirs: installdirs-lib
+
+uninstall: uninstall-lib
+
+clean distclean: clean-lib
+	rm -f $(OBJS)
diff --git a/src/interfaces/libpq-oauth/README b/src/interfaces/libpq-oauth/README
new file mode 100644
index 00000000000..5006f405080
--- /dev/null
+++ b/src/interfaces/libpq-oauth/README
@@ -0,0 +1,18 @@
+libpq-oauth is an optional module implementing the Device Authorization flow for
+OAuth clients (RFC 8628). It was originally developed as part of libpq core and
+later split out as its own shared library in order to isolate its dependency on
+libcurl. (End users who don't want the Curl dependency can simply choose not to
+install this module.)
+
+If a connection string allows the use of OAuth, the server asks for it, and a
+libpq client has not installed its own custom OAuth flow, libpq will attempt to
+delay-load this module using dlopen() and the following ABI. Failure to load
+results in a failed connection.
+
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
+without warning, even during minor releases (however unlikely). The compiled
+version of libpq-oauth should always match the compiled version of libpq.
+
+TODO
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..3787b388e04
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,4 @@
+# src/interfaces/libpq-oauth/exports.txt
+pg_fe_run_oauth_flow      1
+pg_fe_cleanup_oauth_flow  2
+pg_g_threadlock           3
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..1834afbf7a5
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,32 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+if not libcurl.found() or host_system == 'windows'
+  subdir_done()
+endif
+
+libpq_oauth_sources = files(
+  'oauth-curl.c',
+  'oauth-utils.c',
+)
+
+export_file = custom_target('libpq-oauth.exports',
+  kwargs: gen_export_kwargs,
+)
+
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION. (We still put the major version into the name, to make it
+# obvious where the library belongs.)
+libpq_oauth_so = shared_module('libpq-oauth-' + pg_version_major.to_string(),
+  libpq_oauth_sources,
+  include_directories: [libpq_oauth_inc, postgres_inc],
+  c_pch: pch_postgres_fe_h,
+  dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+  link_depends: export_file,
+  link_args: export_fmt.format(export_file.full_path()),
+  kwargs: default_lib_args,
+)
+
+subdir('po', if_found: libintl)
diff --git a/src/interfaces/libpq-oauth/nls.mk b/src/interfaces/libpq-oauth/nls.mk
new file mode 100644
index 00000000000..eab3347ef60
--- /dev/null
+++ b/src/interfaces/libpq-oauth/nls.mk
@@ -0,0 +1,15 @@
+# src/interfaces/libpq-oauth/nls.mk
+CATALOG_NAME     = libpq-oauth
+GETTEXT_FILES    = oauth-curl.c \
+                   oauth-utils.c
+GETTEXT_TRIGGERS = actx_error:2 \
+                   libpq_append_conn_error:2 \
+                   libpq_append_error:2 \
+                   libpq_gettext \
+                   libpq_ngettext:1,2
+GETTEXT_FLAGS    = actx_error:2:c-format \
+                   libpq_append_conn_error:2:c-format \
+                   libpq_append_error:2:c-format \
+                   libpq_gettext:1:pass-c-format \
+                   libpq_ngettext:1:pass-c-format \
+                   libpq_ngettext:2:pass-c-format
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
similarity index 99%
rename from src/interfaces/libpq/fe-auth-oauth-curl.c
rename to src/interfaces/libpq-oauth/oauth-curl.c
index cd9c0323bb6..11d17ec1597 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -1,6 +1,6 @@
 /*-------------------------------------------------------------------------
  *
- * fe-auth-oauth-curl.c
+ * oauth-curl.c
  *	   The libcurl implementation of OAuth/OIDC authentication, using the
  *	   OAuth Device Authorization Grant (RFC 8628).
  *
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  src/interfaces/libpq/fe-auth-oauth-curl.c
+ *	  src/interfaces/libpq-oauth/oauth-curl.c
  *
  *-------------------------------------------------------------------------
  */
@@ -31,6 +31,8 @@
 #include "fe-auth-oauth.h"
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
+#include "oauth-curl.h"
+#include "oauth-utils.h"
 
 /*
  * It's generally prudent to set a maximum response size to buffer in memory,
@@ -2487,8 +2489,9 @@ prompt_user(struct async_ctx *actx, PGconn *conn)
 		.verification_uri_complete = actx->authz.verification_uri_complete,
 		.expires_in = actx->authz.expires_in,
 	};
+	PQauthDataHook_type hook = PQgetAuthDataHook();
 
-	res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
+	res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
 
 	if (!res)
 	{
diff --git a/src/interfaces/libpq-oauth/oauth-curl.h b/src/interfaces/libpq-oauth/oauth-curl.h
new file mode 100644
index 00000000000..bcc1e737dcd
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-curl.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-curl.h
+ *
+ *	  Definitions for OAuth Device Authorization module
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-curl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_CURL_H
+#define OAUTH_CURL_H
+
+#include "libpq-fe.h"
+
+extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+#endif							/* OAUTH_CURL_H */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.c b/src/interfaces/libpq-oauth/oauth-utils.c
new file mode 100644
index 00000000000..81f9c6dc247
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -0,0 +1,190 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.c
+ *
+ *	  "Glue" helpers providing a copy of some internal APIs from libpq. At
+ *	  some point in the future, we might be able to deduplicate.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq-oauth/oauth-utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-int.h"
+#include "oauth-utils.h"
+#include "pg_config_paths.h"
+
+pgthreadlock_t pg_g_threadlock;
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it.  A newline is automatically appended; the
+ * format should not end with a newline.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+	int			save_errno = errno;
+	bool		done;
+	va_list		args;
+
+	Assert(fmt[strlen(fmt) - 1] != '\n');
+
+	if (PQExpBufferBroken(&conn->errorMessage))
+		return;					/* already failed */
+
+	/* Loop in case we have to retry after enlarging the buffer. */
+	do
+	{
+		errno = save_errno;
+		va_start(args, fmt);
+		done = appendPQExpBufferVA(&conn->errorMessage, libpq_gettext(fmt), args);
+		va_end(args);
+	} while (!done);
+
+	appendPQExpBufferChar(&conn->errorMessage, '\n');
+}
+
+/*
+ * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
+ */
+bool
+oauth_unsafe_debugging_enabled(void)
+{
+	const char *env = getenv("PGOAUTHDEBUG");
+
+	return (env && strcmp(env, "UNSAFE") == 0);
+}
+
+int
+pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
+{
+	sigset_t	sigpipe_sigset;
+	sigset_t	sigset;
+
+	sigemptyset(&sigpipe_sigset);
+	sigaddset(&sigpipe_sigset, SIGPIPE);
+
+	/* Block SIGPIPE and save previous mask for later reset */
+	SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
+	if (SOCK_ERRNO)
+		return -1;
+
+	/* We can have a pending SIGPIPE only if it was blocked before */
+	if (sigismember(osigset, SIGPIPE))
+	{
+		/* Is there a pending SIGPIPE? */
+		if (sigpending(&sigset) != 0)
+			return -1;
+
+		if (sigismember(&sigset, SIGPIPE))
+			*sigpipe_pending = true;
+		else
+			*sigpipe_pending = false;
+	}
+	else
+		*sigpipe_pending = false;
+
+	return 0;
+}
+
+void
+pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
+{
+	int			save_errno = SOCK_ERRNO;
+	int			signo;
+	sigset_t	sigset;
+
+	/* Clear SIGPIPE only if none was pending */
+	if (got_epipe && !sigpipe_pending)
+	{
+		if (sigpending(&sigset) == 0 &&
+			sigismember(&sigset, SIGPIPE))
+		{
+			sigset_t	sigpipe_sigset;
+
+			sigemptyset(&sigpipe_sigset);
+			sigaddset(&sigpipe_sigset, SIGPIPE);
+
+			sigwait(&sigpipe_sigset, &signo);
+		}
+	}
+
+	/* Restore saved block mask */
+	pthread_sigmask(SIG_SETMASK, osigset, NULL);
+
+	SOCK_ERRNO_SET(save_errno);
+}
+
+#ifdef ENABLE_NLS
+
+static void
+libpq_binddomain(void)
+{
+	/*
+	 * At least on Windows, there are gettext implementations that fail if
+	 * multiple threads call bindtextdomain() concurrently.  Use a mutex and
+	 * flag variable to ensure that we call it just once per process.  It is
+	 * not known that similar bugs exist on non-Windows platforms, but we
+	 * might as well do it the same way everywhere.
+	 */
+	static volatile bool already_bound = false;
+	static pthread_mutex_t binddomain_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+	if (!already_bound)
+	{
+		/* bindtextdomain() does not preserve errno */
+#ifdef WIN32
+		int			save_errno = GetLastError();
+#else
+		int			save_errno = errno;
+#endif
+
+		(void) pthread_mutex_lock(&binddomain_mutex);
+
+		if (!already_bound)
+		{
+			const char *ldir;
+
+			/*
+			 * No relocatable lookup here because the calling executable could
+			 * be anywhere
+			 */
+			ldir = getenv("PGLOCALEDIR");
+			if (!ldir)
+				ldir = LOCALEDIR;
+			bindtextdomain(PG_TEXTDOMAIN("libpq-oauth"), ldir);
+			already_bound = true;
+		}
+
+		(void) pthread_mutex_unlock(&binddomain_mutex);
+
+#ifdef WIN32
+		SetLastError(save_errno);
+#else
+		errno = save_errno;
+#endif
+	}
+}
+
+char *
+libpq_gettext(const char *msgid)
+{
+	libpq_binddomain();
+	return dgettext(PG_TEXTDOMAIN("libpq-oauth"), msgid);
+}
+
+char *
+libpq_ngettext(const char *msgid, const char *msgid_plural, unsigned long n)
+{
+	libpq_binddomain();
+	return dngettext(PG_TEXTDOMAIN("libpq-oauth"), msgid, msgid_plural, n);
+}
+
+#endif							/* ENABLE_NLS */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
new file mode 100644
index 00000000000..e5bd6b28b11
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.h
+ *
+ *	  Definitions providing missing libpq internal APIs
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "libpq-int.h"
+
+extern PGDLLEXPORT pgthreadlock_t pg_g_threadlock;
+
+void		libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+bool		oauth_unsafe_debugging_enabled(void);
+int			pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+void		pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
diff --git a/src/interfaces/libpq-oauth/po/LINGUAS b/src/interfaces/libpq-oauth/po/LINGUAS
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/interfaces/libpq-oauth/po/meson.build b/src/interfaces/libpq-oauth/po/meson.build
new file mode 100644
index 00000000000..61b3807ac68
--- /dev/null
+++ b/src/interfaces/libpq-oauth/po/meson.build
@@ -0,0 +1,3 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+nls_targets += [i18n.gettext('libpq-oauth-' + pg_version_major.to_string())]
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 90b0b65db6f..8cf8d9e54d8 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -64,10 +64,6 @@ OBJS += \
 	fe-secure-gssapi.o
 endif
 
-ifeq ($(with_libcurl),yes)
-OBJS += fe-auth-oauth-curl.o
-endif
-
 ifeq ($(PORTNAME), cygwin)
 override shlib = cyg$(NAME)$(DLSUFFIX)
 endif
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..0625cf39e9a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,4 @@ PQsetAuthDataHook         207
 PQgetAuthDataHook         208
 PQdefaultAuthDataHook     209
 PQfullProtocolVersion     210
+appendPQExpBufferVA       211
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index cf1a25e2ccc..ce15a5e8de1 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -15,6 +15,10 @@
 
 #include "postgres_fe.h"
 
+#ifndef WIN32
+#include <dlfcn.h>
+#endif
+
 #include "common/base64.h"
 #include "common/hmac.h"
 #include "common/jsonapi.h"
@@ -721,6 +725,44 @@ cleanup_user_oauth_flow(PGconn *conn)
 	state->async_ctx = NULL;
 }
 
+static bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+#ifdef WIN32
+	return false;
+#else
+	PostgresPollingStatusType (*flow) (PGconn *conn);
+	void		(*cleanup) (PGconn *conn);
+	pgthreadlock_t *threadlock_copy;
+
+	/* libpq-oauth is versioned in lockstep; we don't export a stable ABI. */
+	state->builtin_flow = dlopen("libpq-oauth-" PG_MAJORVERSION DLSUFFIX,
+								 RTLD_NOW | RTLD_LOCAL);
+	if (!state->builtin_flow)
+	{
+		fprintf(stderr, "failed dlopen: %s\n", dlerror()); // XXX
+		return false;
+	}
+
+	flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow");
+	cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow");
+	threadlock_copy = dlsym(state->builtin_flow, "pg_g_threadlock");
+
+	if (!(flow && cleanup && threadlock_copy))
+	{
+		fprintf(stderr, "failed dlsym: %s\n", dlerror()); // XXX
+		dlclose(state->builtin_flow);
+		return false;
+	}
+
+	conn->async_auth = flow;
+	conn->cleanup_async_auth = cleanup;
+	*threadlock_copy = pg_g_threadlock;
+
+	return true;
+#endif							/* !WIN32 */
+}
+
 /*
  * Chooses an OAuth client flow for the connection, which will retrieve a Bearer
  * token for presentation to the server.
@@ -792,18 +834,10 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
 		libpq_append_conn_error(conn, "user-defined OAuth flow failed");
 		goto fail;
 	}
-	else
+	else if (!use_builtin_flow(conn, state))
 	{
-#if USE_LIBCURL
-		/* Hand off to our built-in OAuth flow. */
-		conn->async_auth = pg_fe_run_oauth_flow;
-		conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
-
-#else
 		libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
 		goto fail;
-
-#endif
 	}
 
 	return true;
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 3f1a7503a01..699ba42acc2 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -33,10 +33,10 @@ typedef struct
 
 	PGconn	   *conn;
 	void	   *async_ctx;
+
+	void	   *builtin_flow;
 } fe_oauth_state;
 
-extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
-extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
 extern void pqClearOAuthToken(PGconn *conn);
 extern bool oauth_unsafe_debugging_enabled(void);
 
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 292fecf3320..47d38e9378f 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -38,10 +38,6 @@ if gssapi.found()
   )
 endif
 
-if libcurl.found()
-  libpq_sources += files('fe-auth-oauth-curl.c')
-endif
-
 export_file = custom_target('libpq.exports',
   kwargs: gen_export_kwargs,
 )
-- 
2.34.1

