[
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],
*** 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
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+LIBCURL_LDLIBS
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
LIBNUMA_LIBS
LIBNUMA_CFLAGS
with_libnuma
+LIBCURL_LDFLAGS
+LIBCURL_CPPFLAGS
LIBCURL_LIBS
LIBCURL_CFLAGS
with_libcurl
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
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"
{ $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
*** 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
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
##
# 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])
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
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-side OAuth is not supported on this platform])
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
+
+ You need
Curl to build an optional module
+ which implements the OAuth Device
+ Authorization flow for client applications.
+
+
+
You need
LZ4 , if you want to support
OAuth Support
- libpq implements support for the OAuth v2 Device Authorization client flow,
+
libpq implements support for the OAuth v2 Device Authorization client flow,
documented in
- which it will attempt to use by default if the server
+ as an optional module. See the
+ installation documentation for information on how to enable support
+ for Device Authorization as a builtin flow.
+
+ When support is enabled and the optional module installed,
libpq
+ will use the builtin flow by default if the server
requests a bearer token during
authentication. This flow can be utilized even if the system running the
client application does not have a usable web browser, for example when
- running a client via
SSH . Client applications may implement their own flows
- instead; see .
+ running a client via
SSH .
The builtin flow will, by default, print a URL to visit and a user code to
they match expectations, before continuing. Permissions should not be given
to untrusted third parties.
+ Client applications may implement their own flows to customize interaction
+ and integration with applications. See
+ for more information on how add a custom flow to
libpq .
+
For an OAuth client flow to be usable, the connection string must at minimum
contain and
- The OAuth Device Authorization flow included in
libpq
+ The OAuth Device Authorization flow which
+ can be included
requires the end user to visit a URL with a browser, then enter a code
which permits
libpq to connect to the server
on their behalf. The default prompt simply prints the
This callback is only invoked during the builtin device
authorization flow. If the application installs a
custom OAuth
- flow, this authdata type will not be used.
+ flow, or
libpq was not built with
+ support for the builtin flow, this authdata type will not be used.
If a non-NULL verification_uri_complete is
- Replaces the entire OAuth flow with a custom implementation. The hook
- should either directly return a Bearer token for the current
+ Adds a custom implementation of a flow, replacing the builtin flow if
+ it is installed.
+ The hook should either directly return a Bearer token for the current
user/issuer/scope combination, if one is available without blocking, or
else set up an asynchronous callback to retrieve one.
backend_both_deps = []
backend_deps = []
libpq_deps = []
+libpq_oauth_deps = []
pg_sysroot = ''
###############################################################
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
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-side OAuth is not supported on this platform')
+ endif
+
else
libcurl = not_found_dep
endif
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(
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@
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
--- /dev/null
+#-------------------------------------------------------------------------
+#
+# 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. The
+# staticlib doesn't need version information in its name.
+override shlib := lib$(NAME)$(DLSUFFIX)
+override stlib := libpq-oauth.a
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(LIBCURL_CPPFLAGS) $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES)
+
+OBJS_STATIC = oauth-curl.o
+
+# The shared library needs additional glue symbols.
+OBJS_SHLIB = \
+ oauth-curl_shlib.o \
+ oauth-utils.o \
+
+oauth-utils.o: override CPPFLAGS += -DUSE_DYNAMIC_OAUTH
+oauth-curl_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+
+# Add shlib-/stlib-specific objects.
+$(shlib): override OBJS += $(OBJS_SHLIB)
+$(shlib): $(OBJS_SHLIB)
+
+$(stlib): override OBJS += $(OBJS_STATIC)
+$(stlib): $(OBJS_STATIC)
+
+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
+
+# 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 $@
+
+# 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) $(OBJS_STATIC) $(OBJS_SHLIB)
--- /dev/null
+libpq-oauth is an optional module implementing the Device Authorization flow for
+OAuth clients (RFC 8628). It is maintained 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.
+
+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:
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_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);
+
+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. (libpq.a and libpq-oauth.a must be
+part of the same build. Unlike the dynamic module, there are no translation
+shims provided.)
--- /dev/null
+# src/interfaces/libpq-oauth/exports.txt
+libpq_oauth_init 1
+pg_fe_run_oauth_flow 2
+pg_fe_cleanup_oauth_flow 3
--- /dev/null
+# 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',
+)
+libpq_oauth_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
+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')
+
+libpq_oauth_st = static_library('libpq-oauth',
+ 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,
+)
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_args: libpq_so_c_args,
+ 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,
+)
/*-------------------------------------------------------------------------
*
- * fe-auth- oauth-curl.c
+ * oauth-curl.c
* The libcurl implementation of OAuth/OIDC authentication, using the
* OAuth Device Authorization Grant (RFC 8628).
*
* 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
*
*-------------------------------------------------------------------------
*/
#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"
+
+#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 dynamic builds of libpq-oauth
+#endif
/*
* It's generally prudent to set a maximum response size to buffer in memory,
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)
{
state->async_ctx = NULL;
}
- conn->altsock = PGINVALID_SOCKET ;
+ set_conn_altsock(conn, PGINVALID_SOCKET) ;
}
/*
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);
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
actx->mux = kqueue();
if (actx->mux < 0)
{
}
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;
}
/*
{
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;
}
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};
}
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;
}
/*
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)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev;
#ifdef __NetBSD__
}
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;
}
/*
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)
/* 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? */
}
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;
}
/*
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() */
/*---
* 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;
}
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,
* 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)
{
* 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;
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;
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() */
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
};
+ PQauthDataHook_type hook = PQgetAuthDataHook();
- res = PQauthDataH ook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
+ res = h ook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
if (!res)
{
static PostgresPollingStatusType
pg_fe_run_oauth_flow_impl(PGconn *conn)
{
- 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;
do
{
/* By default, the multiplexer is the altsock. Reassign as desired. */
- conn->altsock = actx->mux ;
+ set_conn_altsock(conn, actx->mux) ;
switch (actx->step)
{
*/
if (!timer_expired(actx))
{
- conn->altsock = actx->timerfd ;
+ set_conn_altsock(conn, actx->timerfd) ;
return PGRES_POLLING_READING;
}
{
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;
actx->step = OAUTH_STEP_DISCOVERY;
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)
{
/*
actx->user_prompted = true;
}
- if (conn-> oauth_token)
+ if (oauth_token)
break; /* done! */
/*
* 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;
* 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);
/*
* Assemble the three parts of our error: context, body, and detail. See
* also the documentation for struct async_ctx.
*/
if (actx->errctx)
- {
- appendPQExpBufferStr(&conn->errorMessage,
- libpq_gettext(actx->errctx));
- appendPQExpBufferStr(&conn->errorMessage, ": ");
- }
+ appendPQExpBuffer(errbuf, "%s: ", libpq_gettext(actx->errctx));
if (PQExpBufferDataBroken(actx->errbuf))
- appendPQExpBufferStr(&conn->errorMessage,
- libpq_gettext("out of memory"));
+ appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
else
- appendPQExpBufferStr(&conn->errorMessage , actx->errbuf.data);
+ appendPQExpBufferStr(errbuf , actx->errbuf.data);
if (actx->curl_err[0])
{
- size_t len;
-
- appendPQExpBuffer(&conn->errorMessage,
- " (libcurl: %s)", actx->curl_err);
+ appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
/* Sometimes libcurl adds a newline to the error buffer. :( */
- len = conn->errorMessage.len;
- if (len >= 2 && conn->errorMessage.data[len - 2] == '\n')
+ if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
{
- conn->errorMessage.data[ len - 2] = ')';
- conn->errorMessage.data[ len - 1] = '\0';
- conn->errorMessage. len--;
+ errbuf->data[errbuf-> len - 2] = ')';
+ errbuf->data[errbuf-> len - 1] = '\0';
+ errbuf-> len--;
}
}
- appendPQExpBufferChar(&conn->errorMessage , '\n');
+ appendPQExpBufferChar(errbuf , '\n');
return PGRES_POLLING_FAILED;
}
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * 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 */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * 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 "oauth-utils.h"
+
+#ifndef USE_DYNAMIC_OAUTH
+#error oauth-utils.c is not supported in static builds
+#endif
+
+#ifdef LIBPQ_INT_H
+#error do not rely on libpq-int.h in dynamic builds of 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.
+ *
+ * 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
+ *
+ * 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_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;
+}
+
+/*
+ * 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 didn't enable NLS but the libpq-oauth
+ * build did. That's an odd mismatch, but we can handle it.
+ *
+ * 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);
+}
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * 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 "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);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_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);
+
+/*
+ * 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;
+
+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 */
OBJS = \
$(WIN32RES) \
- fe-auth-oauth.o \
fe-auth-scram.o \
fe-cancel.o \
fe-connect.o \
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)
# 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 -l socket -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
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
+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().)
# 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
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
$(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)'
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
PQgetAuthDataHook 208
PQdefaultAuthDataHook 209
PQfullProtocolVersion 210
+appendPQExpBufferVA 211
#include "postgres_fe.h"
+#ifdef USE_DYNAMIC_OAUTH
+#endif
+
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
#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,
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);
+
+/*
+ * 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.)
+ */
+
+#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
+ * 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)
+{
+ static bool initialized = false;
+ static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
+ int lockerr;
+
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_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);
+
+ /*
+ * On macOS only, load the module using its absolute install path; the
+ * standard search behavior is not very helpful for this use case. Unlike
+ * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
+ * absolute paths (modulo SIP effects), so tests can continue to work.
+ *
+ * On the other platforms, load the module using only the basename, to
+ * rely on the runtime linker's standard search behavior.
+ */
+ const char *const module_name =
+#if defined(__darwin__)
+ LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#else
+ "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#endif
+
+ state->builtin_flow = dlopen(module_name, 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;
+ }
+
+ /*
+ * Past this point, we do not unload the module. It stays in the process
+ * permanently.
+ */
+
+ /*
+ * We need to inject necessary function pointers into the module. This
+ * only needs to be done once -- even if the pointers are constant,
+ * assigning them while another thread is executing the flows feels like
+ * tempting fate.
+ */
+ if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
+ {
+ /* Should not happen... but don't continue if it does. */
+ Assert(false);
+
+ libpq_append_conn_error(conn, "failed to lock mutex (%d)", lockerr);
+ return false;
+ }
+
+ if (!initialized)
+ {
+ init(pg_g_threadlock,
+#ifdef ENABLE_NLS
+ libpq_gettext,
+#else
+ NULL,
+#endif
+ 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;
+ }
+
+ pthread_mutex_unlock(&init_mutex);
+
+ /* 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.
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");
+ libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
goto fail;
-
-#endif
}
return true;
#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
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;
+
+ 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;
)
endif
-if libcurl.found()
- libpq_sources += files('fe-auth-oauth-curl.c')
-endif
-
export_file = custom_target('libpq.exports',
kwargs: gen_export_kwargs,
)
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
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' : '',
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',
+ ]
+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(
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
'LIBNUMA_CFLAGS', 'LIBNUMA_LIBS',
'LIBURING_CFLAGS', 'LIBURING_LIBS',
+
+ 'LIBCURL_CPPFLAGS', 'LIBCURL_LDFLAGS', 'LIBCURL_LDLIBS',
]
if host_system == 'windows' and cc.get_argument_syntax() != 'msvc'
],
'env': {
'PYTHON': python.path(),
- 'with_libcurl': libcurl.found() ? 'yes' : 'no',
+ 'with_libcurl': oauth_flow_supported ? 'yes' : 'no',
'with_python': 'yes',
},
},
"fails without custom hook installed",
flags => ["--no-hook"],
expected_stderr =>
- qr/no custom OAuth flows are available, and libpq was not built with libcurl support /
+ qr/no OAuth flows are available \(try installing the libpq-oauth package\) /
);
}