From 1e500c077aef3dfd17c5423d9f63639a75c2fd80 Mon Sep 17 00:00:00 2001 From: Jacob Champion Date: Wed, 26 Mar 2025 10:55:28 -0700 Subject: [PATCH v5] WIP: split Device Authorization flow into dlopen'd module See notes on mailing list. Co-authored-by: Daniel Gustafsson --- config/programs.m4 | 17 +- configure | 50 ++++- configure.ac | 26 ++- meson.build | 22 +- src/Makefile.global.in | 3 + src/interfaces/Makefile | 12 ++ src/interfaces/libpq-oauth/Makefile | 65 ++++++ src/interfaces/libpq-oauth/README | 30 +++ src/interfaces/libpq-oauth/exports.txt | 4 + src/interfaces/libpq-oauth/meson.build | 54 +++++ .../oauth-curl.c} | 60 +++--- src/interfaces/libpq-oauth/oauth-curl.h | 24 +++ src/interfaces/libpq-oauth/oauth-utils.c | 202 ++++++++++++++++++ src/interfaces/libpq-oauth/oauth-utils.h | 35 +++ src/interfaces/libpq/Makefile | 20 +- src/interfaces/libpq/exports.txt | 1 + src/interfaces/libpq/fe-auth-oauth-dynamic.c | 109 ++++++++++ src/interfaces/libpq/fe-auth-oauth-static.c | 40 ++++ src/interfaces/libpq/fe-auth-oauth.c | 26 ++- src/interfaces/libpq/fe-auth-oauth.h | 5 +- src/interfaces/libpq/meson.build | 8 +- src/interfaces/libpq/nls.mk | 12 +- src/makefiles/meson.build | 2 + 23 files changed, 747 insertions(+), 80 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} (98%) 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/fe-auth-oauth-dynamic.c create mode 100644 src/interfaces/libpq/fe-auth-oauth-static.c diff --git a/config/programs.m4 b/config/programs.m4 index 0a07feb37cc..0ad1e58b48d 100644 --- a/config/programs.m4 +++ b/config/programs.m4 @@ -286,9 +286,20 @@ AC_DEFUN([PGAC_CHECK_LIBCURL], [ AC_CHECK_HEADER(curl/curl.h, [], [AC_MSG_ERROR([header file is required for --with-libcurl])]) - AC_CHECK_LIB(curl, curl_multi_init, [], + AC_CHECK_LIB(curl, curl_multi_init, [ + AC_DEFINE([HAVE_LIBCURL], [1], [Define to 1 if you have the `curl' library (-lcurl).]) + AC_SUBST(LIBCURL_LDLIBS, -lcurl) + ], [AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])]) + pgac_save_CPPFLAGS=$CPPFLAGS + pgac_save_LDFLAGS=$LDFLAGS + pgac_save_LIBS=$LIBS + + CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS" + LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS" + LIBS="$LIBCURL_LDLIBS $LIBS" + # Check to see whether the current platform supports threadsafe Curl # initialization. AC_CACHE_CHECK([for curl_global_init thread safety], [pgac_cv__libcurl_threadsafe_init], @@ -338,4 +349,8 @@ AC_DEFUN([PGAC_CHECK_LIBCURL], *** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order *** to use it with libpq.]) fi + + CPPFLAGS=$pgac_save_CPPFLAGS + LDFLAGS=$pgac_save_LDFLAGS + LIBS=$pgac_save_LIBS ])# PGAC_CHECK_LIBCURL diff --git a/configure b/configure index 8f4a5ab28ec..df1da549c4c 100755 --- a/configure +++ b/configure @@ -655,6 +655,7 @@ UUID_LIBS LDAP_LIBS_BE LDAP_LIBS_FE with_ssl +LIBCURL_LDLIBS PTHREAD_CFLAGS PTHREAD_LIBS PTHREAD_CC @@ -708,6 +709,8 @@ XML2_LIBS XML2_CFLAGS XML2_CONFIG with_libxml +LIBCURL_LDFLAGS +LIBCURL_CPPFLAGS LIBCURL_LIBS LIBCURL_CFLAGS with_libcurl @@ -9042,19 +9045,27 @@ $as_echo "yes" >&6; } fi - # We only care about -I, -D, and -L switches; - # note that -lcurl will be added by PGAC_CHECK_LIBCURL below. + # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use + # them only for libpq-oauth. + LIBCURL_CPPFLAGS= + LIBCURL_LDFLAGS= + + # We only care about -I, -D, and -L switches. Note that -lcurl will be added + # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below. for pgac_option in $LIBCURL_CFLAGS; do case $pgac_option in - -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";; + -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";; esac done for pgac_option in $LIBCURL_LIBS; do case $pgac_option in - -L*) LDFLAGS="$LDFLAGS $pgac_option";; + -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";; esac done + + + # OAuth requires python for testing if test "$with_python" != yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** OAuth support tests require --with-python to run" >&5 @@ -12517,9 +12528,6 @@ fi fi -# 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? if test "$with_libcurl" = yes ; then ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default" @@ -12567,17 +12575,26 @@ fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_multi_init" >&5 $as_echo "$ac_cv_lib_curl_curl_multi_init" >&6; } if test "x$ac_cv_lib_curl_curl_multi_init" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE_LIBCURL 1 -_ACEOF - LIBS="-lcurl $LIBS" + +$as_echo "#define HAVE_LIBCURL 1" >>confdefs.h + + LIBCURL_LDLIBS=-lcurl + else as_fn_error $? "library 'curl' does not provide curl_multi_init" "$LINENO" 5 fi + pgac_save_CPPFLAGS=$CPPFLAGS + pgac_save_LDFLAGS=$LDFLAGS + pgac_save_LIBS=$LIBS + + CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS" + LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS" + LIBS="$LIBCURL_LDLIBS $LIBS" + # Check to see whether the current platform supports threadsafe Curl # initialization. { $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init thread safety" >&5 @@ -12681,6 +12698,10 @@ $as_echo "$pgac_cv__libcurl_async_dns" >&6; } *** to use it with libpq." "$LINENO" 5 fi + CPPFLAGS=$pgac_save_CPPFLAGS + LDFLAGS=$pgac_save_LDFLAGS + LIBS=$pgac_save_LIBS + fi if test "$with_gssapi" = yes ; then @@ -14329,6 +14350,13 @@ done fi +if test "$with_libcurl" = yes ; then + # Error out early if this platform can't support libpq-oauth. + if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then + as_fn_error $? "client OAuth is not supported on this platform" "$LINENO" 5 + fi +fi + ## ## Types, structures, compiler characteristics ## diff --git a/configure.ac b/configure.ac index fc5f7475d07..218aeea1b3b 100644 --- a/configure.ac +++ b/configure.ac @@ -1033,19 +1033,27 @@ if test "$with_libcurl" = yes ; then # to explicitly set TLS 1.3 ciphersuites). PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.61.0]) - # We only care about -I, -D, and -L switches; - # note that -lcurl will be added by PGAC_CHECK_LIBCURL below. + # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use + # them only for libpq-oauth. + LIBCURL_CPPFLAGS= + LIBCURL_LDFLAGS= + + # We only care about -I, -D, and -L switches. Note that -lcurl will be added + # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below. for pgac_option in $LIBCURL_CFLAGS; do case $pgac_option in - -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";; + -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";; esac done for pgac_option in $LIBCURL_LIBS; do case $pgac_option in - -L*) LDFLAGS="$LDFLAGS $pgac_option";; + -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";; esac done + AC_SUBST(LIBCURL_CPPFLAGS) + AC_SUBST(LIBCURL_LDFLAGS) + # OAuth requires python for testing if test "$with_python" != yes; then AC_MSG_WARN([*** OAuth support tests require --with-python to run]) @@ -1340,9 +1348,6 @@ failure. It is possible the compiler isn't looking in the proper directory. Use --without-zlib to disable zlib support.])]) fi -# 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? if test "$with_libcurl" = yes ; then PGAC_CHECK_LIBCURL fi @@ -1640,6 +1645,13 @@ if test "$PORTNAME" = "win32" ; then AC_CHECK_HEADERS(crtdefs.h) fi +if test "$with_libcurl" = yes ; then + # Error out early if this platform can't support libpq-oauth. + if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then + AC_MSG_ERROR([client OAuth is not supported on this platform]) + fi +fi + ## ## Types, structures, compiler characteristics ## diff --git a/meson.build b/meson.build index 27717ad8976..0f0df9b1af4 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 = '' @@ -2587,6 +2588,7 @@ header_checks = [ 'xlocale.h', ] +header_macros = {} foreach header : header_checks varname = 'HAVE_' + header.underscorify().to_upper() @@ -2595,6 +2597,15 @@ foreach header : header_checks include_directories: postgres_inc, args: test_c_args) cdata.set(varname, found ? 1 : false, description: 'Define to 1 if you have the <@0@> header file.'.format(header)) + + # Mixing 1/false in cdata means we can't perform equality checks using + # cdata.get(), though, so store our defined header macros for later lookup. + # + # https://github.com/mesonbuild/meson/issues/11581 + # + if found + header_macros += {varname: true} + endif endforeach @@ -3251,17 +3262,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/Makefile.global.in b/src/Makefile.global.in index 737b2dd1869..eb9b5de75b4 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -343,6 +343,9 @@ perl_embed_ldflags = @perl_embed_ldflags@ AWK = @AWK@ LN_S = @LN_S@ +LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@ +LIBCURL_LDFLAGS = @LIBCURL_LDFLAGS@ +LIBCURL_LDLIBS = @LIBCURL_LDLIBS@ MSGFMT = @MSGFMT@ MSGFMT_FLAGS = @MSGFMT_FLAGS@ MSGMERGE = @MSGMERGE@ 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..5fd251a1d27 --- /dev/null +++ b/src/interfaces/libpq-oauth/Makefile @@ -0,0 +1,65 @@ +#------------------------------------------------------------------------- +# +# 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 = pq-oauth-$(MAJORVERSION) + +# Force the name "libpq-oauth" for both the static and shared libraries. +override shlib := lib$(NAME)$(DLSUFFIX) +override stlib := lib$(NAME).a + +override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(LIBCURL_CPPFLAGS) $(CPPFLAGS) + +OBJS = \ + $(WIN32RES) \ + oauth-curl.o + +# The shared library needs additional glue symbols. +$(shlib): OBJS += oauth-utils.o +$(shlib): oauth-utils.o + +SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib) +SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS) +SHLIB_PREREQS = submake-libpq +SHLIB_EXPORTS = exports.txt + +# Disable -bundle_loader on macOS. +BE_DLLLIBS = + +# By default, a library without an SONAME doesn't get a static library, so we +# add it to the build explicitly. +all: all-lib all-static-lib + +# Shared library stuff +include $(top_srcdir)/src/Makefile.shlib + +# Ignore the standard rules for SONAME-less installation; we want both the +# static and shared libraries to go into libdir. +install: all installdirs $(stlib) $(shlib) + $(INSTALL_SHLIB) $(shlib) '$(DESTDIR)$(libdir)/$(shlib)' + $(INSTALL_STLIB) $(stlib) '$(DESTDIR)$(libdir)/$(stlib)' + +installdirs: + $(MKDIR_P) '$(DESTDIR)$(libdir)' + +uninstall: + rm -f '$(DESTDIR)$(libdir)/$(stlib)' + rm -f '$(DESTDIR)$(libdir)/$(shlib)' + +clean distclean: clean-lib + rm -f $(OBJS) oauth-utils.o 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..7634b7f6fb1 --- /dev/null +++ b/src/interfaces/libpq-oauth/meson.build @@ -0,0 +1,54 @@ +# Copyright (c) 2022-2025, PostgreSQL Global Development Group + +oauth_flow_supported = ( + libcurl.found() + and (header_macros.has_key('HAVE_SYS_EVENT_H') + or header_macros.has_key('HAVE_SYS_EPOLL_H')) +) + +if libcurlopt.disabled() + subdir_done() +elif not oauth_flow_supported + if libcurlopt.enabled() + error('client OAuth is not supported on this platform') + endif + subdir_done() +endif + +libpq_oauth_sources = files( + 'oauth-curl.c', +) + +# The shared library needs additional glue symbols. +libpq_oauth_so_sources = files( + '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_st = static_library(libpq_oauth_name, + libpq_oauth_sources, + include_directories: [libpq_oauth_inc, postgres_inc], + c_pch: pch_postgres_fe_h, + dependencies: [frontend_stlib_code, libpq_oauth_deps], + kwargs: default_lib_args, +) + +libpq_oauth_so = shared_module(libpq_oauth_name, + libpq_oauth_sources + libpq_oauth_so_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 98% rename from src/interfaces/libpq/fe-auth-oauth-curl.c rename to src/interfaces/libpq-oauth/oauth-curl.c index cd9c0323bb6..d52125415bc 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 * *------------------------------------------------------------------------- */ @@ -17,20 +17,23 @@ #include #include -#ifdef HAVE_SYS_EPOLL_H +#include + +#if defined(HAVE_SYS_EPOLL_H) #include #include -#endif -#ifdef HAVE_SYS_EVENT_H +#elif defined(HAVE_SYS_EVENT_H) #include +#else +#error libpq-oauth is not supported on this platform #endif -#include #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, @@ -1110,7 +1113,7 @@ parse_access_token(struct async_ctx *actx, struct token *tok) static bool setup_multiplexer(struct async_ctx *actx) { -#ifdef HAVE_SYS_EPOLL_H +#if defined(HAVE_SYS_EPOLL_H) struct epoll_event ev = {.events = EPOLLIN}; actx->mux = epoll_create1(EPOLL_CLOEXEC); @@ -1134,8 +1137,7 @@ setup_multiplexer(struct async_ctx *actx) } return true; -#endif -#ifdef HAVE_SYS_EVENT_H +#elif defined(HAVE_SYS_EVENT_H) actx->mux = kqueue(); if (actx->mux < 0) { @@ -1158,10 +1160,9 @@ setup_multiplexer(struct async_ctx *actx) } return true; +#else +#error setup_multiplexer is not implemented on this platform #endif - - actx_error(actx, "libpq does not support the Device Authorization flow on this platform"); - return false; } /* @@ -1174,7 +1175,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx, { struct async_ctx *actx = ctx; -#ifdef HAVE_SYS_EPOLL_H +#if defined(HAVE_SYS_EPOLL_H) struct epoll_event ev = {0}; int res; int op = EPOLL_CTL_ADD; @@ -1230,8 +1231,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx, } return 0; -#endif -#ifdef HAVE_SYS_EVENT_H +#elif defined(HAVE_SYS_EVENT_H) struct kevent ev[2] = {0}; struct kevent ev_out[2]; struct timespec timeout = {0}; @@ -1312,10 +1312,9 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx, } return 0; +#else +#error register_socket is not implemented on this platform #endif - - actx_error(actx, "libpq does not support multiplexer sockets on this platform"); - return -1; } /* @@ -1334,7 +1333,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx, static bool set_timer(struct async_ctx *actx, long timeout) { -#if HAVE_SYS_EPOLL_H +#if defined(HAVE_SYS_EPOLL_H) struct itimerspec spec = {0}; if (timeout < 0) @@ -1363,8 +1362,7 @@ set_timer(struct async_ctx *actx, long timeout) } return true; -#endif -#ifdef HAVE_SYS_EVENT_H +#elif defined(HAVE_SYS_EVENT_H) struct kevent ev; #ifdef __NetBSD__ @@ -1419,10 +1417,9 @@ set_timer(struct async_ctx *actx, long timeout) } return true; +#else +#error set_timer is not implemented on this platform #endif - - actx_error(actx, "libpq does not support timers on this platform"); - return false; } /* @@ -1433,7 +1430,7 @@ set_timer(struct async_ctx *actx, long timeout) static int timer_expired(struct async_ctx *actx) { -#if HAVE_SYS_EPOLL_H +#if defined(HAVE_SYS_EPOLL_H) struct itimerspec spec = {0}; if (timerfd_gettime(actx->timerfd, &spec) < 0) @@ -1453,8 +1450,7 @@ timer_expired(struct async_ctx *actx) /* If the remaining time to expiration is zero, we're done. */ return (spec.it_value.tv_sec == 0 && spec.it_value.tv_nsec == 0); -#endif -#ifdef HAVE_SYS_EVENT_H +#elif defined(HAVE_SYS_EVENT_H) int res; /* Is the timer queue ready? */ @@ -1466,10 +1462,9 @@ timer_expired(struct async_ctx *actx) } return (res > 0); +#else +#error timer_expired is not implemented on this platform #endif - - actx_error(actx, "libpq does not support timers on this platform"); - return -1; } /* @@ -2487,8 +2482,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..2bdbf904743 --- /dev/null +++ b/src/interfaces/libpq-oauth/oauth-utils.c @@ -0,0 +1,202 @@ +/*------------------------------------------------------------------------- + * + * 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 + +#include "libpq-int.h" +#include "oauth-utils.h" + +static libpq_gettext_func libpq_gettext_impl; +static conn_errorMessage_func conn_errorMessage; + +pgthreadlock_t pg_g_threadlock; + +/*- + * 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..852b1948ecb 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 @@ -86,7 +82,7 @@ endif # that are built correctly for use in a shlib. SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib ifneq ($(PORTNAME), win32) -SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lcurl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS) +SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS) else SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE) endif @@ -107,6 +103,15 @@ all: all-lib libpq-refs-stamp include $(top_srcdir)/src/Makefile.shlib backend_src = $(top_srcdir)/src/backend +# Add the correct implementations of the OAuth flow, if requested, for both +# shared and static builds. +ifeq ($(with_libcurl),yes) +$(shlib): OBJS += fe-auth-oauth-dynamic.o +$(shlib): fe-auth-oauth-dynamic.o +$(stlib): OBJS += fe-auth-oauth-static.o +$(stlib): fe-auth-oauth-static.o +endif + # Check for functions that libpq must not call, currently just exit(). # (Ideally we'd reject abort() too, but there are various scenarios where # build toolchains insert abort() calls, e.g. to implement assert().) @@ -115,8 +120,6 @@ backend_src = $(top_srcdir)/src/backend # which seems to insert references to that even in pure C code. Excluding # __tsan_func_exit is necessary when using ThreadSanitizer data race detector # which use this function for instrumentation of function exit. -# libcurl registers an exit handler in the memory debugging code when running -# with LeakSanitizer. # Skip the test when profiling, as gcc may insert exit() calls for that. # Also skip the test on platforms where libpq infrastructure may be provided # by statically-linked libraries, as we can't expect them to honor this @@ -124,7 +127,7 @@ backend_src = $(top_srcdir)/src/backend libpq-refs-stamp: $(shlib) ifneq ($(enable_coverage), yes) ifeq (,$(filter solaris,$(PORTNAME))) - @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit -e _atexit | grep exit; then \ + @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \ echo 'libpq must not be calling any function which invokes exit'; exit 1; \ fi endif @@ -172,5 +175,6 @@ clean distclean: clean-lib $(MAKE) -C test $@ rm -rf tmp_check rm -f $(OBJS) pthread.h libpq-refs-stamp + rm -f fe-auth-oauth-dynamic.o fe-auth-oauth-static.o # Might be left over from a Win32 client-only build rm -f pg_config_paths.h 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-dynamic.c b/src/interfaces/libpq/fe-auth-oauth-dynamic.c new file mode 100644 index 00000000000..a84551a6307 --- /dev/null +++ b/src/interfaces/libpq/fe-auth-oauth-dynamic.c @@ -0,0 +1,109 @@ +/*------------------------------------------------------------------------- + * + * fe-auth-oauth-dynamic.c + * + * Implements the builtin flow by loading the libpq-oauth plugin. + * See also fe-auth-oauth-static.c, for static builds. + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/interfaces/libpq/fe-auth-oauth-dynamic.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#ifndef USE_LIBCURL +#error this should only be compiled when OAuth support is enabled +#endif + +#include + +#include "fe-auth-oauth.h" + +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; +} + +/* + * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its + * callbacks into the connection's async auth handlers. + * + * Failure to load here results in a relatively quiet connection error, to + * handle the use case where the build supports loading a flow but a user does + * not want to install it. Troubleshooting of linker/loader failures can be done + * via PGOAUTHDEBUG. + */ +bool +use_builtin_flow(PGconn *conn, fe_oauth_state *state) +{ + 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; +} diff --git a/src/interfaces/libpq/fe-auth-oauth-static.c b/src/interfaces/libpq/fe-auth-oauth-static.c new file mode 100644 index 00000000000..25119bbb50c --- /dev/null +++ b/src/interfaces/libpq/fe-auth-oauth-static.c @@ -0,0 +1,40 @@ +/*------------------------------------------------------------------------- + * + * fe-auth-oauth-static.c + * + * Implements the builtin flow using the libpq-oauth.a staticlib. + * See also fe-auth-oauth-dynamic.c, which loads a plugin at runtime. + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/interfaces/libpq/fe-auth-oauth-static.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#ifndef USE_LIBCURL +#error this should only be compiled when OAuth support is enabled +#endif + +#include "fe-auth-oauth.h" + +/* see libpq-oauth/oauth-curl.h */ +extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn); +extern void pg_fe_cleanup_oauth_flow(PGconn *conn); + +/* + * Loads the builtin flow from libpq-oauth.a. + */ +bool +use_builtin_flow(PGconn *conn, fe_oauth_state *state) +{ + /* Set our asynchronous callbacks. */ + conn->async_auth = pg_fe_run_oauth_flow; + conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow; + + return true; +} diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c index cf1a25e2ccc..0bad132b580 100644 --- a/src/interfaces/libpq/fe-auth-oauth.c +++ b/src/interfaces/libpq/fe-auth-oauth.c @@ -22,6 +22,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 +722,21 @@ cleanup_user_oauth_flow(PGconn *conn) state->async_ctx = NULL; } +#ifndef USE_LIBCURL + +/* + * This configuration doesn't support the builtin flow. + * + * Alternative implementations are in fe-auth-oauth-dynamic/-static.c. + */ +bool +use_builtin_flow(PGconn *conn, fe_oauth_state *state) +{ + return false; +} + +#endif + /* * Chooses an OAuth client flow for the connection, which will retrieve a Bearer * token for presentation to the server. @@ -792,18 +808,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..687e664475f 100644 --- a/src/interfaces/libpq/fe-auth-oauth.h +++ b/src/interfaces/libpq/fe-auth-oauth.h @@ -33,12 +33,13 @@ 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); +extern bool use_builtin_flow(PGconn *conn, fe_oauth_state *state); /* Mechanisms in fe-auth-oauth.c */ extern const pg_fe_sasl_mech pg_oauth_mech; diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build index 292fecf3320..bf880e053f8 100644 --- a/src/interfaces/libpq/meson.build +++ b/src/interfaces/libpq/meson.build @@ -18,6 +18,7 @@ libpq_sources = files( 'pqexpbuffer.c', ) libpq_so_sources = [] # for shared lib, in addition to the above +libpq_st_sources = [] # for static lib, in addition to the above if host_system == 'windows' libpq_sources += files('pthread-win32.c', 'win32.c') @@ -38,8 +39,11 @@ if gssapi.found() ) endif +# Add the correct implementations of the OAuth flow, if requested, for both +# shared and static builds. if libcurl.found() - libpq_sources += files('fe-auth-oauth-curl.c') + libpq_so_sources += files('fe-auth-oauth-dynamic.c') + libpq_st_sources += files('fe-auth-oauth-static.c') endif export_file = custom_target('libpq.exports', @@ -59,7 +63,7 @@ libpq_c_args = ['-DSO_MAJOR_VERSION=5'] # more complexity than its worth (reusing object files requires also linking # to the library on windows or breaks precompiled headers). libpq_st = static_library('libpq', - libpq_sources, + libpq_sources + libpq_st_sources, include_directories: [libpq_inc], c_args: libpq_c_args, c_pch: pch_postgres_fe_h, 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 diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build index 46d8da070e8..f2ba5b38124 100644 --- a/src/makefiles/meson.build +++ b/src/makefiles/meson.build @@ -201,6 +201,8 @@ pgxs_empty = [ 'ICU_LIBS', 'LIBURING_CFLAGS', 'LIBURING_LIBS', + + 'LIBCURL_CPPFLAGS', 'LIBCURL_LDFLAGS', 'LIBCURL_LDLIBS', ] if host_system == 'windows' and cc.get_argument_syntax() != 'msvc' -- 2.34.1