since-v9.diff.txt
text/plain
Filename: since-v9.diff.txt
Type: text/plain
Part: 0
1: 5f87f11b18e < -: ----------- Add minor-version counterpart to (PG_)MAJORVERSION
2: 9e37fd7c217 ! 1: e86e93f7ac8 oauth: Move the builtin flow into a separate module
@@ Commit message
The default flow relies on some libpq internals. Some of these can be
safely duplicated (such as the SIGPIPE handlers), but others need to be
- shared between libpq and libpq-oauth for thread-safety. To avoid exporting
- these internals to all libpq clients forever, these dependencies are
- instead injected from the libpq side via an initialization function.
- This also lets libpq communicate the offset of conn->errorMessage to
- libpq-oauth, so that we can function without crashing if the module on
- the search path came from a different build of Postgres.
+ shared between libpq and libpq-oauth for thread-safety. To avoid
+ exporting these internals to all libpq clients forever, these
+ dependencies are instead injected from the libpq side via an
+ initialization function. This also lets libpq communicate the offsets of
+ PGconn struct members to libpq-oauth, so that we can function without
+ crashing if the module on the search path came from a different build of
+ Postgres. (A minor-version upgrade could swap the libpq-oauth module out
+ from under a long-running libpq client before it does its first load of
+ the OAuth flow.)
This ABI is considered "private". The module has no SONAME or version
- symlinks, and it's named libpq-oauth-<major>-<minor>.so to avoid mixing
- and matching across Postgres versions, in case internal struct order
- needs to change. (Future improvements may promote this "OAuth flow
- plugin" to a first-class concept, at which point we would need a public
- API to replace this anyway.)
+ symlinks, and it's named libpq-oauth-<major>.so to avoid mixing and
+ matching across Postgres versions. (Future improvements may promote this
+ "OAuth flow plugin" to a first-class concept, at which point we would
+ need a public API to replace this anyway.)
Additionally, NLS support for error messages in b3f0be788a was
incomplete, because the new error macros weren't being scanned by
@@ src/interfaces/libpq-oauth/Makefile (new)
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
-+NAME = pq-oauth-$(MAJORVERSION)-$(MINORVERSION)
++NAME = pq-oauth-$(MAJORVERSION)
+
+# Force the name "libpq-oauth" for both the static and shared libraries. The
+# staticlib doesn't need version information in its name.
@@ src/interfaces/libpq-oauth/README (new)
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
-+across releases; the name of the module (libpq-oauth-MAJOR-MINOR) reflects this.
++across major releases; the name of the module (libpq-oauth-MAJOR) reflects this.
+The module exports the following symbols:
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
@@ src/interfaces/libpq-oauth/README (new)
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
-+ conn_errorMessage_func errmsg_impl);
++ conn_errorMessage_func errmsg_impl,
++ conn_oauth_client_id_func clientid_impl,
++ conn_oauth_client_secret_func clientsecret_impl,
++ conn_oauth_discovery_uri_func discoveryuri_impl,
++ conn_oauth_issuer_id_func issuerid_impl,
++ conn_oauth_scope_func scope_impl,
++ conn_sasl_state_func saslstate_impl,
++ set_conn_altsock_func setaltsock_impl,
++ set_conn_oauth_token_func settoken_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 using this initialization
+function before the flow is run.
+
-+It also relies on libpq to expose conn->errorMessage, via the errmsg_impl. This
-+is done to decouple the module ABI from the offset of errorMessage, which can
-+change positions depending on configure-time options. This way we can safely
-+search the standard dlopen() paths (e.g. RPATH, LD_LIBRARY_PATH, the SO cache)
-+for an implementation module to use, even if that module wasn't compiled at the
-+same time as libpq.
++It also relies on access to several members of the PGconn struct. Not only can
++these change positions across minor versions, but the offsets aren't necessarily
++stable within a single minor release (conn->errorMessage, for instance, can
++change offsets depending on configure-time options). Therefore the necessary
++accessors (named conn_*) and mutators (set_conn_*) are injected here. With this
++approach, we can safely search the standard dlopen() paths (e.g. RPATH,
++LD_LIBRARY_PATH, the SO cache) for an implementation module to use, even if that
++module wasn't compiled at the same time as libpq -- which becomes especially
++important during "live upgrade" situations where a running libpq application has
++the libpq-oauth module updated out from under it before it's first loaded from
++disk.
+
+= Static Build =
+
+The static library libpq.a does not perform any dynamic loading. If the builtin
+flow is enabled, the application is expected to link against libpq-oauth.a
-+directly to provide the necessary symbols.
++directly to provide the necessary symbols. (libpq.a and libpq-oauth.a must be
++part of the same build. Unlike the dynamic module, there are no translation
++shims provided.)
## src/interfaces/libpq-oauth/exports.txt (new) ##
@@
@@ src/interfaces/libpq-oauth/meson.build (new)
+
+# 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@-@1@'.format(pg_version_major, pg_version_minor)
++libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
@@ src/interfaces/libpq/fe-auth-oauth-curl.c => src/interfaces/libpq-oauth/oauth-cu
-#include <unistd.h>
#include "common/jsonapi.h"
- #include "fe-auth.h"
+-#include "fe-auth.h"
#include "fe-auth-oauth.h"
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
++
+#ifdef USE_DYNAMIC_OAUTH
++
++/*
++ * The module build is decoupled from libpq-int.h, to try to avoid inadvertent
++ * ABI breaks during minor version bumps. Replacements for the missing internals
++ * are provided by oauth-utils.
++ */
+#include "oauth-utils.h"
++
++#else /* !USE_DYNAMIC_OAUTH */
++
++/*
++ * Static builds may rely on PGconn offsets directly. Keep these aligned with
++ * the bank of callbacks in oauth-utils.h.
++ */
++#include "libpq-int.h"
++
++#define conn_errorMessage(CONN) (&CONN->errorMessage)
++#define conn_oauth_client_id(CONN) (CONN->oauth_client_id)
++#define conn_oauth_client_secret(CONN) (CONN->oauth_client_secret)
++#define conn_oauth_discovery_uri(CONN) (CONN->oauth_discovery_uri)
++#define conn_oauth_issuer_id(CONN) (CONN->oauth_issuer_id)
++#define conn_oauth_scope(CONN) (CONN->oauth_scope)
++#define conn_sasl_state(CONN) (CONN->sasl_state)
++
++#define set_conn_altsock(CONN, VAL) do { CONN->altsock = VAL; } while (0)
++#define set_conn_oauth_token(CONN, VAL) do { CONN->oauth_token = VAL; } while (0)
++
++#endif /* !USE_DYNAMIC_OAUTH */
++
++/* One final guardrail against accidental inclusion... */
++#if defined(USE_DYNAMIC_OAUTH) && defined(LIBPQ_INT_H)
++#error do not rely on libpq-int.h in libpq-oauth.so
+#endif
/*
* It's generally prudent to set a maximum response size to buffer in memory,
+@@ src/interfaces/libpq-oauth/oauth-curl.c: free_async_ctx(PGconn *conn, struct async_ctx *actx)
+ void
+ pg_fe_cleanup_oauth_flow(PGconn *conn)
+ {
+- fe_oauth_state *state = conn->sasl_state;
++ fe_oauth_state *state = conn_sasl_state(conn);
+
+ if (state->async_ctx)
+ {
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_cleanup_oauth_flow(PGconn *conn)
+ state->async_ctx = NULL;
+ }
+
+- conn->altsock = PGINVALID_SOCKET;
++ set_conn_altsock(conn, PGINVALID_SOCKET);
+ }
+
+ /*
@@ src/interfaces/libpq-oauth/oauth-curl.c: parse_access_token(struct async_ctx *actx, struct token *tok)
static bool
setup_multiplexer(struct async_ctx *actx)
@@ src/interfaces/libpq-oauth/oauth-curl.c: timer_expired(struct async_ctx *actx)
}
/*
+@@ src/interfaces/libpq-oauth/oauth-curl.c: static bool
+ check_issuer(struct async_ctx *actx, PGconn *conn)
+ {
+ const struct provider *provider = &actx->provider;
++ const char *oauth_issuer_id = conn_oauth_issuer_id(conn);
+
+- Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
++ Assert(oauth_issuer_id); /* ensured by setup_oauth_parameters() */
+ Assert(provider->issuer); /* ensured by parse_provider() */
+
+ /*---
+@@ src/interfaces/libpq-oauth/oauth-curl.c: check_issuer(struct async_ctx *actx, PGconn *conn)
+ * sent to. This comparison MUST use simple string comparison as defined
+ * in Section 6.2.1 of [RFC3986].
+ */
+- if (strcmp(conn->oauth_issuer_id, provider->issuer) != 0)
++ if (strcmp(oauth_issuer_id, provider->issuer) != 0)
+ {
+ actx_error(actx,
+ "the issuer identifier (%s) does not match oauth_issuer (%s)",
+- provider->issuer, conn->oauth_issuer_id);
++ provider->issuer, oauth_issuer_id);
+ return false;
+ }
+
+@@ src/interfaces/libpq-oauth/oauth-curl.c: check_for_device_flow(struct async_ctx *actx)
+ static bool
+ add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
+ {
++ const char *oauth_client_id = conn_oauth_client_id(conn);
++ const char *oauth_client_secret = conn_oauth_client_secret(conn);
++
+ bool success = false;
+ char *username = NULL;
+ char *password = NULL;
+
+- if (conn->oauth_client_secret) /* Zero-length secrets are permitted! */
++ if (oauth_client_secret) /* Zero-length secrets are permitted! */
+ {
+ /*----
+ * Use HTTP Basic auth to send the client_id and secret. Per RFC 6749,
+@@ src/interfaces/libpq-oauth/oauth-curl.c: add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *c
+ * would it be redundant, but some providers in the wild (e.g. Okta)
+ * refuse to accept it.
+ */
+- username = urlencode(conn->oauth_client_id);
+- password = urlencode(conn->oauth_client_secret);
++ username = urlencode(oauth_client_id);
++ password = urlencode(oauth_client_secret);
+
+ if (!username || !password)
+ {
+@@ src/interfaces/libpq-oauth/oauth-curl.c: add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *c
+ * If we're not otherwise authenticating, client_id is REQUIRED in the
+ * request body.
+ */
+- build_urlencoded(reqbody, "client_id", conn->oauth_client_id);
++ build_urlencoded(reqbody, "client_id", oauth_client_id);
+
+ CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, goto cleanup);
+ actx->used_basic_auth = false;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: cleanup:
+ static bool
+ start_device_authz(struct async_ctx *actx, PGconn *conn)
+ {
++ const char *oauth_scope = conn_oauth_scope(conn);
+ const char *device_authz_uri = actx->provider.device_authorization_endpoint;
+ PQExpBuffer work_buffer = &actx->work_data;
+
+- Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
++ Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
+ Assert(device_authz_uri); /* ensured by check_for_device_flow() */
+
+ /* Construct our request body. */
+ resetPQExpBuffer(work_buffer);
+- if (conn->oauth_scope && conn->oauth_scope[0])
+- build_urlencoded(work_buffer, "scope", conn->oauth_scope);
++ if (oauth_scope && oauth_scope[0])
++ build_urlencoded(work_buffer, "scope", oauth_scope);
+
+ if (!add_client_identification(actx, work_buffer, conn))
+ return false;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: start_token_request(struct async_ctx *actx, PGconn *conn)
+ const char *device_code = actx->authz.device_code;
+ PQExpBuffer work_buffer = &actx->work_data;
+
+- Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
++ Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
+ Assert(token_uri); /* ensured by parse_provider() */
+ Assert(device_code); /* ensured by parse_device_authz() */
+
@@ src/interfaces/libpq-oauth/oauth-curl.c: prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
@@ src/interfaces/libpq-oauth/oauth-curl.c: prompt_user(struct async_ctx *actx, PGc
if (!res)
{
-@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+@@ src/interfaces/libpq-oauth/oauth-curl.c: done:
+ static PostgresPollingStatusType
+ pg_fe_run_oauth_flow_impl(PGconn *conn)
{
- fe_oauth_state *state = conn->sasl_state;
+- fe_oauth_state *state = conn->sasl_state;
++ fe_oauth_state *state = conn_sasl_state(conn);
struct async_ctx *actx;
++ char *oauth_token = NULL;
+ PQExpBuffer errbuf;
if (!initialize_curl(conn))
return PGRES_POLLING_FAILED;
@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ do
+ {
+ /* By default, the multiplexer is the altsock. Reassign as desired. */
+- conn->altsock = actx->mux;
++ set_conn_altsock(conn, actx->mux);
+
+ switch (actx->step)
+ {
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ */
+ if (!timer_expired(actx))
+ {
+- conn->altsock = actx->timerfd;
++ set_conn_altsock(conn, actx->timerfd);
+ return PGRES_POLLING_READING;
+ }
- error_return:
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ {
+ case OAUTH_STEP_INIT:
+ actx->errctx = "failed to fetch OpenID discovery document";
+- if (!start_discovery(actx, conn->oauth_discovery_uri))
++ if (!start_discovery(actx, conn_oauth_discovery_uri(conn)))
+ goto error_return;
-+ /*
-+ * For the dynamic module build, we can't safely rely on the offset of
-+ * conn->errorMessage, since it depends on build options like USE_SSL et
-+ * al. libpq gives us a translator function instead.
-+ */
-+#ifdef USE_DYNAMIC_OAUTH
+ actx->step = OAUTH_STEP_DISCOVERY;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ break;
+
+ case OAUTH_STEP_TOKEN_REQUEST:
+- if (!handle_token_response(actx, &conn->oauth_token))
++ if (!handle_token_response(actx, &oauth_token))
+ goto error_return;
+
++ /*
++ * Hook any oauth_token into the PGconn immediately so that
++ * the allocation isn't lost in case of an error.
++ */
++ set_conn_oauth_token(conn, oauth_token);
++
+ if (!actx->user_prompted)
+ {
+ /*
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ actx->user_prompted = true;
+ }
+
+- if (conn->oauth_token)
++ if (oauth_token)
+ break; /* done! */
+
+ /*
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ * the client wait directly on the timerfd rather than the
+ * multiplexer.
+ */
+- conn->altsock = actx->timerfd;
++ set_conn_altsock(conn, actx->timerfd);
+
+ actx->step = OAUTH_STEP_WAIT_INTERVAL;
+ actx->running = 1;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ * point, actx->running will be set. But there are some corner cases
+ * where we can immediately loop back around; see start_request().
+ */
+- } while (!conn->oauth_token && !actx->running);
++ } while (!oauth_token && !actx->running);
+
+ /* If we've stored a token, we're done. Otherwise come back later. */
+- return conn->oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
++ return oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
+
+ error_return:
+ errbuf = conn_errorMessage(conn);
-+#else
-+ errbuf = &conn->errorMessage;
-+#endif
-+
+
/*
* Assemble the three parts of our error: context, body, and detail. See
* also the documentation for struct async_ctx.
@@ src/interfaces/libpq-oauth/oauth-utils.c (new)
+
+#include <signal.h>
+
-+#include "libpq-int.h"
+#include "oauth-utils.h"
+
+#ifndef USE_DYNAMIC_OAUTH
+#error oauth-utils.c is not supported in static builds
+#endif
+
-+static libpq_gettext_func libpq_gettext_impl;
++#ifdef LIBPQ_INT_H
++#error do not rely on libpq-int.h in libpq-oauth
++#endif
++
++/*
++ * Function pointers set by libpq_oauth_init().
++ */
+
+pgthreadlock_t pg_g_threadlock;
++static libpq_gettext_func libpq_gettext_impl;
++
+conn_errorMessage_func conn_errorMessage;
++conn_oauth_client_id_func conn_oauth_client_id;
++conn_oauth_client_secret_func conn_oauth_client_secret;
++conn_oauth_discovery_uri_func conn_oauth_discovery_uri;
++conn_oauth_issuer_id_func conn_oauth_issuer_id;
++conn_oauth_scope_func conn_oauth_scope;
++conn_sasl_state_func conn_sasl_state;
++
++set_conn_altsock_func set_conn_altsock;
++set_conn_oauth_token_func set_conn_oauth_token;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
@@ src/interfaces/libpq-oauth/oauth-utils.c (new)
+ *
+ * - 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().
++ * The implementation also needs access to several members of the PGconn struct,
++ * which are not guaranteed to stay in place across minor versions. Accessors
++ * (named conn_*) and mutators (named set_conn_*) are injected here.
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
-+ conn_errorMessage_func errmsg_impl)
++ conn_errorMessage_func errmsg_impl,
++ conn_oauth_client_id_func clientid_impl,
++ conn_oauth_client_secret_func clientsecret_impl,
++ conn_oauth_discovery_uri_func discoveryuri_impl,
++ conn_oauth_issuer_id_func issuerid_impl,
++ conn_oauth_scope_func scope_impl,
++ conn_sasl_state_func saslstate_impl,
++ set_conn_altsock_func setaltsock_impl,
++ set_conn_oauth_token_func settoken_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
++ conn_oauth_client_id = clientid_impl;
++ conn_oauth_client_secret = clientsecret_impl;
++ conn_oauth_discovery_uri = discoveryuri_impl;
++ conn_oauth_issuer_id = issuerid_impl;
++ conn_oauth_scope = scope_impl;
++ conn_sasl_state = saslstate_impl;
++ set_conn_altsock = setaltsock_impl;
++ set_conn_oauth_token = settoken_impl;
+}
+
+/*
@@ src/interfaces/libpq-oauth/oauth-utils.h (new)
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
++#include "fe-auth-oauth.h"
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
++/*
++ * A bank of callbacks to safely access members of PGconn, which are all passed
++ * to libpq_oauth_init() by libpq.
++ *
++ * Keep these aligned with the definitions in fe-auth-oauth.c as well as the
++ * static declarations in oauth-curl.c.
++ */
++#define DECLARE_GETTER(TYPE, MEMBER) \
++ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
++ extern conn_ ## MEMBER ## _func conn_ ## MEMBER;
++
++#define DECLARE_SETTER(TYPE, MEMBER) \
++ typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
++ extern set_conn_ ## MEMBER ## _func set_conn_ ## MEMBER;
++
++DECLARE_GETTER(PQExpBuffer, errorMessage);
++DECLARE_GETTER(char *, oauth_client_id);
++DECLARE_GETTER(char *, oauth_client_secret);
++DECLARE_GETTER(char *, oauth_discovery_uri);
++DECLARE_GETTER(char *, oauth_issuer_id);
++DECLARE_GETTER(char *, oauth_scope);
++DECLARE_GETTER(fe_oauth_state *, sasl_state);
++
++DECLARE_SETTER(pgsocket, altsock);
++DECLARE_SETTER(char *, oauth_token);
++
++#undef DECLARE_GETTER
++#undef DECLARE_SETTER
++
+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);
++ conn_errorMessage_func errmsg_impl,
++ conn_oauth_client_id_func clientid_impl,
++ conn_oauth_client_secret_func clientsecret_impl,
++ conn_oauth_discovery_uri_func discoveryuri_impl,
++ conn_oauth_issuer_id_func issuerid_impl,
++ conn_oauth_scope_func scope_impl,
++ conn_sasl_state_func saslstate_impl,
++ set_conn_altsock_func setaltsock_impl,
++ set_conn_oauth_token_func settoken_impl);
+
-+/* Callback to safely obtain conn->errorMessage from a PGconn. */
-+extern conn_errorMessage_func conn_errorMessage;
++/*
++ * Duplicated APIs, copied from libpq (primarily libpq-int.h, which we cannot
++ * depend on here).
++ */
++
++typedef enum
++{
++ PG_BOOL_UNKNOWN = 0, /* Currently unknown */
++ PG_BOOL_YES, /* Yes (true) */
++ PG_BOOL_NO /* No (false) */
++} PGTernaryBool;
+
-+/* 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);
+
++#ifdef ENABLE_NLS
++extern char *libpq_gettext(const char *msgid) pg_attribute_format_arg(1);
++#else
++#define libpq_gettext(x) (x)
++#endif
++
++extern pgthreadlock_t pg_g_threadlock;
++
++#define pglock_thread() pg_g_threadlock(true)
++#define pgunlock_thread() pg_g_threadlock(false)
++
+#endif /* OAUTH_UTILS_H */
## src/interfaces/libpq/Makefile ##
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+ */
+
+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.
++ * Define accessor/mutator shims to inject into libpq-oauth, so that it doesn't
++ * depend on the offsets within PGconn. (These have changed during minor version
++ * updates in the past.)
+ */
-+static PQExpBuffer
-+conn_errorMessage(PGconn *conn)
-+{
-+ return &conn->errorMessage;
-+}
++
++#define DEFINE_GETTER(TYPE, MEMBER) \
++ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
++ static TYPE conn_ ## MEMBER(PGconn *conn) { return conn->MEMBER; }
++
++/* Like DEFINE_GETTER, but returns a pointer to the member. */
++#define DEFINE_GETTER_P(TYPE, MEMBER) \
++ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
++ static TYPE conn_ ## MEMBER(PGconn *conn) { return &conn->MEMBER; }
++
++#define DEFINE_SETTER(TYPE, MEMBER) \
++ typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
++ static void set_conn_ ## MEMBER(PGconn *conn, TYPE val) { conn->MEMBER = val; }
++
++DEFINE_GETTER_P(PQExpBuffer, errorMessage);
++DEFINE_GETTER(char *, oauth_client_id);
++DEFINE_GETTER(char *, oauth_client_secret);
++DEFINE_GETTER(char *, oauth_discovery_uri);
++DEFINE_GETTER(char *, oauth_issuer_id);
++DEFINE_GETTER(char *, oauth_scope);
++DEFINE_GETTER(fe_oauth_state *, sasl_state);
++
++DEFINE_SETTER(pgsocket, altsock);
++DEFINE_SETTER(char *, oauth_token);
+
+/*
+ * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
-+ conn_errorMessage_func errmsg_impl);
++ conn_errorMessage_func errmsg_impl,
++ conn_oauth_client_id_func clientid_impl,
++ conn_oauth_client_secret_func clientsecret_impl,
++ conn_oauth_discovery_uri_func discoveryuri_impl,
++ conn_oauth_issuer_id_func issuerid_impl,
++ conn_oauth_scope_func scope_impl,
++ conn_sasl_state_func saslstate_impl,
++ set_conn_altsock_func setaltsock_impl,
++ set_conn_oauth_token_func settoken_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+ */
+ const char *const module_name =
+#if defined(__darwin__)
-+ LIBDIR "/libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
++ LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#else
-+ "libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
++ "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#endif
+
+ state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+#else
+ NULL,
+#endif
-+ conn_errorMessage);
++ conn_errorMessage,
++ conn_oauth_client_id,
++ conn_oauth_client_secret,
++ conn_oauth_discovery_uri,
++ conn_oauth_issuer_id,
++ conn_oauth_scope,
++ conn_sasl_state,
++ set_conn_altsock,
++ set_conn_oauth_token);
+
+ initialized = true;
+ }
@@ src/interfaces/libpq/fe-auth-oauth.c: setup_token_request(PGconn *conn, fe_oauth
return true;
## src/interfaces/libpq/fe-auth-oauth.h ##
-@@ src/interfaces/libpq/fe-auth-oauth.h: typedef struct
+@@
+ #ifndef FE_AUTH_OAUTH_H
+ #define FE_AUTH_OAUTH_H
+
++#include "fe-auth-sasl.h"
+ #include "libpq-fe.h"
+-#include "libpq-int.h"
+
+
+ enum fe_oauth_step
+@@ src/interfaces/libpq/fe-auth-oauth.h: enum fe_oauth_step
+ FE_OAUTH_SERVER_ERROR,
+ };
+
++/*
++ * This struct is exported to the libpq-oauth module. If changes are needed
++ * during backports to stable branches, please keep ABI compatibility (no
++ * changes to existing members, add new members at the end, etc.).
++ */
+ typedef struct
+ {
+ enum fe_oauth_step step;
PGconn *conn;
void *async_ctx;