v3-0001-WIP-split-Device-Authorization-flow-into-dlopen-d.patch
application/octet-stream
Filename: v3-0001-WIP-split-Device-Authorization-flow-into-dlopen-d.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v3-0001
Subject: WIP: split Device Authorization flow into dlopen'd module
| File | + | − |
|---|---|---|
| meson.build | 7 | 5 |
| src/interfaces/libpq/exports.txt | 1 | 0 |
| src/interfaces/libpq/fe-auth-oauth.c | 85 | 9 |
| src/interfaces/libpq/fe-auth-oauth.h | 2 | 2 |
| src/interfaces/libpq/Makefile | 0 | 4 |
| src/interfaces/libpq/meson.build | 0 | 4 |
| src/interfaces/libpq/nls.mk | 9 | 3 |
| src/interfaces/libpq-oauth/exports.txt | 4 | 0 |
| src/interfaces/libpq-oauth/Makefile | 55 | 0 |
| src/interfaces/libpq-oauth/meson.build | 31 | 0 |
| src/interfaces/libpq-oauth/oauth-curl.c | 6 | 4 |
| src/interfaces/libpq-oauth/oauth-curl.h | 24 | 0 |
| src/interfaces/libpq-oauth/oauth-utils.c | 201 | 0 |
| src/interfaces/libpq-oauth/oauth-utils.h | 35 | 0 |
| src/interfaces/libpq-oauth/README | 30 | 0 |
| src/interfaces/Makefile | 12 | 0 |
From 19e6887b43a3d907465b9e36816ef6ef235207a6 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 v3] 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 | 12 ++
src/interfaces/libpq-oauth/Makefile | 55 +++++
src/interfaces/libpq-oauth/README | 30 +++
src/interfaces/libpq-oauth/exports.txt | 4 +
src/interfaces/libpq-oauth/meson.build | 31 +++
.../oauth-curl.c} | 10 +-
src/interfaces/libpq-oauth/oauth-curl.h | 24 +++
src/interfaces/libpq-oauth/oauth-utils.c | 201 ++++++++++++++++++
src/interfaces/libpq-oauth/oauth-utils.h | 35 +++
src/interfaces/libpq/Makefile | 4 -
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-auth-oauth.c | 94 +++++++-
src/interfaces/libpq/fe-auth-oauth.h | 4 +-
src/interfaces/libpq/meson.build | 4 -
src/interfaces/libpq/nls.mk | 12 +-
16 files changed, 502 insertions(+), 31 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
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
diff --git a/meson.build b/meson.build
index 27717ad8976..6f1a8ea55ef 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 = ''
@@ -3251,17 +3252,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..e6822caa206 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,19 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+else
+ALWAYS_SUBDIRS += libpq-oauth
+endif
+
$(recurse)
+$(recurse_always)
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..f44766dd549
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,55 @@
+#-------------------------------------------------------------------------
+#
+# 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.
+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 $(filter -lintl, $(LIBS))
+SHLIB_PREREQS = submake-libpq
+SHLIB_EXPORTS = exports.txt
+
+# Disable -bundle_loader on macOS.
+BE_DLLLIBS =
+
+# 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..ef746617c71
--- /dev/null
+++ b/src/interfaces/libpq-oauth/README
@@ -0,0 +1,30 @@
+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.
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+- void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
+conn->async_auth and conn->cleanup_async_auth, respectively.
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq before the flow is run. It also
+relies on libpq to expose conn->errorMessage, via an errmsg_impl.
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..6891a83dbf9
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,4 @@
+# src/interfaces/libpq-oauth/exports.txt
+libpq_oauth_init 1
+pg_fe_run_oauth_flow 2
+pg_fe_cleanup_oauth_flow 3
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..79916e7aa62
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,31 @@
+# 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.
+libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ 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,
+)
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..759cd494aae 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
*
*-------------------------------------------------------------------------
*/
@@ -29,8 +29,9 @@
#include "common/jsonapi.h"
#include "fe-auth.h"
#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 +2488,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..248d0424ad0
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-curl.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/* Exported async-auth callbacks. */
+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..7a0949c071b
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -0,0 +1,201 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 <signal.h>
+
+#include "libpq-int.h"
+#include "oauth-utils.h"
+
+pgthreadlock_t pg_g_threadlock;
+libpq_gettext_func libpq_gettext_impl;
+conn_errorMessage_func conn_errorMessage;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
+ *
+ * The current implementation relies on the following private implementation
+ * details of libpq:
+ *
+ * - pg_g_threadlock: protects libcurl initialization if the underlying Curl
+ * installation is not threadsafe
+ *
+ * - libpq_gettext: translates error messages using libpq's message domain
+ *
+ * - conn->errorMessage: holds translated errors for the connection. This is
+ * handled through a translation shim, which avoids either depending on the
+ * offset of the errorMessage in PGconn, or needing to export the variadic
+ * libpq_append_conn_error().
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
+}
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it. This is a copy of libpq's internal API.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+ int save_errno = errno;
+ bool done;
+ va_list args;
+ PQExpBuffer errorMessage = conn_errorMessage(conn);
+
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ if (PQExpBufferBroken(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(errorMessage, libpq_gettext(fmt), args);
+ va_end(args);
+ } while (!done);
+
+ appendPQExpBufferChar(errorMessage, '\n');
+}
+
+#ifdef ENABLE_NLS
+
+/*
+ * A shim that defers to the actual libpq_gettext().
+ */
+char *
+libpq_gettext(const char *msgid)
+{
+ if (!libpq_gettext_impl)
+ {
+ /*
+ * Possible if the libpq build doesn't enable NLS. That's a concerning
+ * mismatch, but in this particular case we can handle it. Try to warn
+ * a developer with an assertion, though.
+ */
+ Assert(false);
+
+ /*
+ * Note that callers of libpq_gettext() have to treat the return value
+ * as if it were const, because builds without NLS simply pass through
+ * their argument.
+ */
+ return unconstify(char *, msgid);
+ }
+
+ return libpq_gettext_impl(msgid);
+}
+
+#endif /* ENABLE_NLS */
+
+/*
+ * 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);
+}
+
+/*
+ * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
+ * pq_block/reset_sigpipe().
+ */
+#ifdef WIN32
+#define SOCK_ERRNO (WSAGetLastError())
+#define SOCK_ERRNO_SET(e) WSASetLastError(e)
+#else
+#define SOCK_ERRNO errno
+#define SOCK_ERRNO_SET(e) (errno = (e))
+#endif
+
+/*
+ * Block SIGPIPE for this thread. This is a copy of libpq's internal API.
+ */
+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;
+}
+
+/*
+ * Discard any pending SIGPIPE and reset the signal mask. This is a copy of
+ * libpq's internal API.
+ */
+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);
+}
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
new file mode 100644
index 00000000000..279fc113248
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+/* Duplicated APIs, copied from libpq. */
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
+extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
+
+#endif /* OAUTH_UTILS_H */
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..416964ac335 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"
@@ -22,6 +26,7 @@
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "mb/pg_wchar.h"
+#include "pg_config_paths.h"
/* The exported OAuth callback mechanism. */
static void *oauth_init(PGconn *conn, const char *password,
@@ -721,6 +726,85 @@ cleanup_user_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/*
+ * This shim is injected into libpq-oauth so that it doesn't depend on the
+ * offset of conn->errorMessage.
+ *
+ * TODO: look into exporting libpq_append_conn_error or a comparable API from
+ * libpq, instead.
+ */
+static PQExpBuffer
+conn_errorMessage(PGconn *conn)
+{
+ return &conn->errorMessage;
+}
+
+static bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+#ifdef WIN32
+ return false;
+#else
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
+ state->builtin_flow = dlopen("libpq-oauth-" PG_MAJORVERSION DLSUFFIX,
+ RTLD_NOW | RTLD_LOCAL);
+ if (!state->builtin_flow)
+ {
+ /*
+ * For end users, this probably isn't an error condition, it just
+ * means the flow isn't installed. Developers and package maintainers
+ * may want to debug this via the PGOAUTHDEBUG envvar, though.
+ *
+ * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
+
+ return false;
+ }
+
+ if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
+ || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
+ || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
+ {
+ /*
+ * This is more of an error condition than the one above, but due to
+ * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
+
+ dlclose(state->builtin_flow);
+ return false;
+ }
+
+ /*
+ * Inject necessary function pointers into the module.
+ */
+ init(pg_g_threadlock,
+#ifdef ENABLE_NLS
+ libpq_gettext,
+#else
+ NULL,
+#endif
+ conn_errorMessage);
+
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = flow;
+ conn->cleanup_async_auth = cleanup;
+
+ 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 +876,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,
)
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index ae761265852..b87df277d93 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -13,15 +13,21 @@ GETTEXT_FILES = fe-auth.c \
fe-secure-common.c \
fe-secure-gssapi.c \
fe-secure-openssl.c \
- win32.c
-GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
+ win32.c \
+ ../libpq-oauth/oauth-curl.c \
+ ../libpq-oauth/oauth-utils.c
+GETTEXT_TRIGGERS = actx_error:2 \
+ libpq_append_conn_error:2 \
libpq_append_error:2 \
libpq_gettext \
libpq_ngettext:1,2 \
+ oauth_parse_set_error:2 \
pqInternalNotice:2
-GETTEXT_FLAGS = libpq_append_conn_error:2:c-format \
+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 \
+ oauth_parse_set_error:2:c-format \
pqInternalNotice:2:c-format
--
2.34.1