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--.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-.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 #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 + -+#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;