aboutsummaryrefslogtreecommitdiff
path: root/src/interfaces/libpq/fe-auth-oauth.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/interfaces/libpq/fe-auth-oauth.c')
-rw-r--r--src/interfaces/libpq/fe-auth-oauth.c229
1 files changed, 219 insertions, 10 deletions
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index ab6a45e2aba..9fbff89a21d 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 <dlfcn.h>
+#endif
+
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
@@ -22,6 +26,7 @@
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "mb/pg_wchar.h"
+#include "pg_config_paths.h"
/* The exported OAuth callback mechanism. */
static void *oauth_init(PGconn *conn, const char *password,
@@ -721,6 +726,218 @@ 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);
+
+/*
+ * 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.
@@ -792,18 +1009,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");
+ libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
goto fail;
-
-#endif
}
return true;