From a202bd932ea390c97df10fcbb0cc3b60419453be Mon Sep 17 00:00:00 2001 From: Jacob Champion Date: Wed, 26 Mar 2025 10:55:28 -0700 Subject: [PATCH v6 1/2] 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 | 32 ++- src/Makefile.global.in | 3 + src/interfaces/Makefile | 12 ++ src/interfaces/libpq-oauth/Makefile | 65 ++++++ src/interfaces/libpq-oauth/README | 43 ++++ src/interfaces/libpq-oauth/exports.txt | 4 + src/interfaces/libpq-oauth/meson.build | 43 ++++ .../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 | 36 +++- src/interfaces/libpq/exports.txt | 1 + src/interfaces/libpq/fe-auth-oauth.c | 151 ++++++++++++- src/interfaces/libpq/fe-auth-oauth.h | 5 +- src/interfaces/libpq/meson.build | 28 ++- src/interfaces/libpq/nls.mk | 12 +- src/makefiles/meson.build | 2 + 21 files changed, 763 insertions(+), 88 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 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..b436c362147 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 = '' @@ -860,13 +861,13 @@ endif ############################################################### libcurlopt = get_option('libcurl') +oauth_flow_supported = false + if not libcurlopt.disabled() # Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability # to explicitly set TLS 1.3 ciphersuites). libcurl = dependency('libcurl', version: '>= 7.61.0', required: libcurlopt) if libcurl.found() - cdata.set('USE_LIBCURL', 1) - # Check to see whether the current platform supports thread-safe Curl # initialization. libcurl_threadsafe_init = false @@ -938,6 +939,22 @@ if not libcurlopt.disabled() endif endif + # Check that the current platform supports our builtin flow. This requires + # libcurl and one of either epoll or kqueue. + oauth_flow_supported = ( + libcurl.found() + and (cc.check_header('sys/event.h', required: false, + args: test_c_args, include_directories: postgres_inc) + or cc.check_header('sys/epoll.h', required: false, + args: test_c_args, include_directories: postgres_inc)) + ) + + if oauth_flow_supported + cdata.set('USE_LIBCURL', 1) + elif libcurlopt.enabled() + error('client OAuth is not supported on this platform') + endif + else libcurl = not_found_dep endif @@ -3251,17 +3268,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..45def6c1ab6 --- /dev/null +++ b/src/interfaces/libpq-oauth/README @@ -0,0 +1,43 @@ +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, and 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 +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); +- 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 using this initialization +function before the flow is run. It also relies on libpq to expose +conn->errorMessage, via the errmsg_impl. + +This dependency injection is done to ensure that the module ABI is decoupled +from the internals of `struct pg_conn`. 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. + += 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. 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..cf597e1da1e --- /dev/null +++ b/src/interfaces/libpq-oauth/meson.build @@ -0,0 +1,43 @@ +# Copyright (c) 2022-2025, PostgreSQL Global Development Group + +if not oauth_flow_supported + 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..d4c20066ce4 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -31,7 +31,6 @@ endif OBJS = \ $(WIN32RES) \ - fe-auth-oauth.o \ fe-auth-scram.o \ fe-cancel.o \ fe-connect.o \ @@ -64,9 +63,11 @@ OBJS += \ fe-secure-gssapi.o endif -ifeq ($(with_libcurl),yes) -OBJS += fe-auth-oauth-curl.o -endif +# The OAuth implementation differs depending on the type of library being built. +OBJS_STATIC = fe-auth-oauth.o + +fe-auth-oauth_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH +OBJS_SHLIB = fe-auth-oauth_shlib.o ifeq ($(PORTNAME), cygwin) override shlib = cyg$(NAME)$(DLSUFFIX) @@ -86,7 +87,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 @@ -101,12 +102,26 @@ ifeq ($(with_ssl),openssl) PKG_CONFIG_REQUIRES_PRIVATE = libssl, libcrypto endif +ifeq ($(with_libcurl),yes) +# libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and +# libpq-oauth needs libcurl. Put both into *.private. +PKG_CONFIG_REQUIRES_PRIVATE += libcurl +%.pc: override SHLIB_LINK_INTERNAL += -lpq-oauth-$(MAJORVERSION) +endif + all: all-lib libpq-refs-stamp # Shared library stuff include $(top_srcdir)/src/Makefile.shlib backend_src = $(top_srcdir)/src/backend +# Add shlib-/stlib-specific objects. +$(shlib): override OBJS += $(OBJS_SHLIB) +$(shlib): $(OBJS_SHLIB) + +$(stlib): override OBJS += $(OBJS_STATIC) +$(stlib): $(OBJS_STATIC) + # 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 +130,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 +137,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 @@ -138,6 +151,11 @@ fe-misc.o: fe-misc.c $(top_builddir)/src/port/pg_config_paths.h $(top_builddir)/src/port/pg_config_paths.h: $(MAKE) -C $(top_builddir)/src/port pg_config_paths.h +# Use src/common/Makefile's trick for tracking dependencies of shlib-specific +# objects. +%_shlib.o: %.c %.o + $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@ + install: all installdirs install-lib $(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)' $(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)' @@ -171,6 +189,6 @@ uninstall: uninstall-lib clean distclean: clean-lib $(MAKE) -C test $@ rm -rf tmp_check - rm -f $(OBJS) pthread.h libpq-refs-stamp + rm -f $(OBJS) $(OBJS_SHLIB) $(OBJS_STATIC) pthread.h libpq-refs-stamp # 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.c b/src/interfaces/libpq/fe-auth-oauth.c index cf1a25e2ccc..5c285adccbd 100644 --- a/src/interfaces/libpq/fe-auth-oauth.c +++ b/src/interfaces/libpq/fe-auth-oauth.c @@ -15,6 +15,10 @@ #include "postgres_fe.h" +#ifdef USE_DYNAMIC_OAUTH +#include +#endif + #include "common/base64.h" #include "common/hmac.h" #include "common/jsonapi.h" @@ -721,6 +725,143 @@ cleanup_user_oauth_flow(PGconn *conn) state->async_ctx = NULL; } +/*------------- + * Builtin Flow + * + * There are three potential implementations of use_builtin_flow: + * + * 1) If the OAuth client is disabled at configuration time, return false. + * Dependent clients must provide their own flow. + * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen() + * the libpq-oauth plugin and use its implementation. + * 3) Otherwise, use flow callbacks that are statically linked into the + * executable. + */ + +#if !defined(USE_LIBCURL) + +/* + * This configuration doesn't support the builtin flow. + */ + +bool +use_builtin_flow(PGconn *conn, fe_oauth_state *state) +{ + return false; +} + +#elif defined(USE_DYNAMIC_OAUTH) + +/* + * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime. + */ + +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; +} + +#else + +/* + * Use the builtin flow in libpq-oauth.a (see libpq-oauth/oauth-curl.h). + */ + +extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn); +extern void pg_fe_cleanup_oauth_flow(PGconn *conn); + +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; +} + +#endif /* USE_LIBCURL */ + + /* * Chooses an OAuth client flow for the connection, which will retrieve a Bearer * token for presentation to the server. @@ -792,18 +933,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..63e48d9fcfb 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,10 +39,6 @@ if gssapi.found() ) endif -if libcurl.found() - libpq_sources += files('fe-auth-oauth-curl.c') -endif - export_file = custom_target('libpq.exports', kwargs: gen_export_kwargs, ) @@ -50,6 +47,9 @@ export_file = custom_target('libpq.exports', libpq_inc = include_directories('.', '../../port') libpq_c_args = ['-DSO_MAJOR_VERSION=5'] +# The OAuth implementation differs depending on the type of library being built. +libpq_so_c_args = ['-DUSE_DYNAMIC_OAUTH'] + # Not using both_libraries() here as # 1) resource files should only be in the shared library # 2) we want the .pc file to include a dependency to {pgport,common}_static for @@ -59,7 +59,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, @@ -70,7 +70,7 @@ libpq_st = static_library('libpq', libpq_so = shared_library('libpq', libpq_sources + libpq_so_sources, include_directories: [libpq_inc, postgres_inc], - c_args: libpq_c_args, + c_args: libpq_c_args + libpq_so_c_args, c_pch: pch_postgres_fe_h, version: '5.' + pg_version_major.to_string(), soversion: host_system != 'windows' ? '5' : '', @@ -86,12 +86,26 @@ libpq = declare_dependency( include_directories: [include_directories('.')] ) +private_deps = [ + frontend_stlib_code, + libpq_deps, +] + +if oauth_flow_supported + # libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and + # libpq-oauth needs libcurl. Put both into *.private. + private_deps += [ + libpq_oauth_deps, + '-lpq-oauth-@0@'.format(pg_version_major), + ] +endif + pkgconfig.generate( name: 'libpq', description: 'PostgreSQL libpq library', url: pg_url, libraries: libpq, - libraries_private: [frontend_stlib_code, libpq_deps], + libraries_private: private_deps, ) install_headers( 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