From fb26bb8b21e2c873d5c4cfd8fd54be692e67aaff Mon Sep 17 00:00:00 2001 From: Jacob Champion Date: Wed, 1 Apr 2026 10:58:46 -0700 Subject: [PATCH v3.1 3/6] squash! Split PGOAUTHDEBUG=UNSAFE into multiple options - Switch from a struct to bitflags. (OAUTHDEBUG_UNSAFE_MASK is introduced a commit early to avoid unnecessary work.) --- src/interfaces/libpq-oauth/oauth-utils.h | 2 +- src/interfaces/libpq/fe-auth-oauth.h | 39 +++++++++++++------- src/interfaces/libpq-oauth/oauth-curl.c | 12 +++--- src/interfaces/libpq-oauth/test-oauth-curl.c | 6 +-- src/interfaces/libpq/fe-auth-oauth-debug.c | 25 +++++-------- src/interfaces/libpq/fe-auth-oauth.c | 6 +-- 6 files changed, 46 insertions(+), 44 deletions(-) diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h index dd4e38d525c..64a9235ee85 100644 --- a/src/interfaces/libpq-oauth/oauth-utils.h +++ b/src/interfaces/libpq-oauth/oauth-utils.h @@ -36,7 +36,7 @@ typedef enum PG_BOOL_NO /* No (false) */ } PGTernaryBool; -extern oauth_debug_flags oauth_get_debug_flags(void); +extern uint32 oauth_get_debug_flags(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); diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h index 859975bcd67..a952fea09cf 100644 --- a/src/interfaces/libpq/fe-auth-oauth.h +++ b/src/interfaces/libpq/fe-auth-oauth.h @@ -40,23 +40,36 @@ typedef struct } fe_oauth_state; /* - * Debug flags for PGOAUTHDEBUG environment variable. - * Each flag controls a specific debug feature. + * Debug flags for the PGOAUTHDEBUG environment variable. Each flag controls a + * specific debug feature. OAUTHDEBUG_UNSAFE_* flags require the envvar to have + * a literal "UNSAFE:" prefix. */ -typedef struct oauth_debug_flags -{ - /* UNSAFE features - require UNSAFE: prefix */ - bool http; /* allow HTTP (unencrypted) connections */ - bool trace; /* log HTTP traffic (exposes secrets) */ - /* SAFE features - allowed without UNSAFE: prefix */ - bool fast_retry; /* allow zero-second retry intervals */ - bool poll_counts; /* print poll() statistics */ - bool print_plugin_errors; /* print plugin loading errors */ -} oauth_debug_flags; +/* allow HTTP (unencrypted) connections */ +#define OAUTHDEBUG_UNSAFE_HTTP (1<<0) +/* log HTTP traffic (exposes secrets) */ +#define OAUTHDEBUG_UNSAFE_TRACE (1<<1) +/* allow zero-second retry intervals */ +#define OAUTHDEBUG_UNSAFE_DOS_ENDPOINT (1<<2) + +/* mind the gap in values; see OAUTHDEBUG_UNSAFE_MASK below */ + +/* print PQconnectPoll statistics */ +#define OAUTHDEBUG_CALL_COUNT (1<<16) +/* print plugin loading errors */ +#define OAUTHDEBUG_PLUGIN_ERRORS (1<<17) + +/* all safe and unsafe flags, for the legacy UNSAFE behavior */ +#define OAUTHDEBUG_UNSAFE_ALL ((uint32) ~0) + +/* Flags are divided into "safe" and "unsafe" based on bit position. */ +#define OAUTHDEBUG_UNSAFE_MASK ((uint32) 0x0000FFFF) + +static_assert(OAUTHDEBUG_CALL_COUNT == OAUTHDEBUG_UNSAFE_MASK + 1, + "the first safe OAUTHDEBUG flag should be above OAUTHDEBUG_UNSAFE_MASK"); extern void pqClearOAuthToken(PGconn *conn); -extern oauth_debug_flags oauth_get_debug_flags(void); +extern uint32 oauth_get_debug_flags(void); /* Mechanisms in fe-auth-oauth.c */ extern const pg_fe_sasl_mech pg_oauth_mech; diff --git a/src/interfaces/libpq-oauth/oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c index 564d76cf063..7100824c560 100644 --- a/src/interfaces/libpq-oauth/oauth-curl.c +++ b/src/interfaces/libpq-oauth/oauth-curl.c @@ -274,7 +274,7 @@ struct async_ctx int running; /* is asynchronous work in progress? */ bool user_prompted; /* have we already sent the authz prompt? */ bool used_basic_auth; /* did we send a client secret? */ - oauth_debug_flags debug_flags; /* can we give developer assistance */ + uint32 debug_flags; /* can we give developer assistance? */ int dbg_num_calls; /* (debug mode) how many times were we called? */ }; @@ -1023,7 +1023,7 @@ parse_interval(struct async_ctx *actx, const char *interval_str) parsed = ceil(parsed); if (parsed < 1) - return actx->debug_flags.fast_retry ? 0 : 1; + return (actx->debug_flags & OAUTHDEBUG_UNSAFE_DOS_ENDPOINT) ? 0 : 1; else if (parsed >= INT_MAX) return INT_MAX; @@ -1797,7 +1797,7 @@ setup_curl_handles(struct async_ctx *actx) */ CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L, return false); - if (actx->debug_flags.trace) + if (actx->debug_flags & OAUTHDEBUG_UNSAFE_TRACE) { /* * Set a callback for retrieving error information from libcurl, the @@ -1829,7 +1829,7 @@ setup_curl_handles(struct async_ctx *actx) const long unsafe = CURLPROTO_HTTPS | CURLPROTO_HTTP; #endif - if (actx->debug_flags.http) + if (actx->debug_flags & OAUTHDEBUG_UNSAFE_HTTP) protos = unsafe; CHECK_SETOPT(actx, popt, protos, return false); @@ -2297,7 +2297,7 @@ check_for_device_flow(struct async_ctx *actx) * decent time to bail out if we're not using HTTPS for the endpoints * we'll use for the flow. */ - if (!actx->debug_flags.http) + if ((actx->debug_flags & OAUTHDEBUG_UNSAFE_HTTP) == 0) { if (pg_strncasecmp(provider->device_authorization_endpoint, HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0) @@ -3027,7 +3027,7 @@ pg_fe_run_oauth_flow(PGconn *conn, struct PGoauthBearerRequest *request, * drain_timer_events(), when we're in debug mode, track the total number * of calls to this function and print that at the end of the flow. */ - if (actx && actx->debug_flags.poll_counts) + if (actx->debug_flags & OAUTHDEBUG_CALL_COUNT) { actx->dbg_num_calls++; if (result == PGRES_POLLING_OK || result == PGRES_POLLING_FAILED) diff --git a/src/interfaces/libpq-oauth/test-oauth-curl.c b/src/interfaces/libpq-oauth/test-oauth-curl.c index 06815be9a0a..185c17e5807 100644 --- a/src/interfaces/libpq-oauth/test-oauth-curl.c +++ b/src/interfaces/libpq-oauth/test-oauth-curl.c @@ -89,11 +89,7 @@ init_test_actx(void) actx->mux = PGINVALID_SOCKET; actx->timerfd = -1; - actx->debug_flags.http = true; - actx->debug_flags.trace = true; - actx->debug_flags.fast_retry = true; - actx->debug_flags.poll_counts = true; - actx->debug_flags.print_plugin_errors = true; + actx->debug_flags = OAUTHDEBUG_UNSAFE_ALL; initPQExpBuffer(&actx->errbuf); diff --git a/src/interfaces/libpq/fe-auth-oauth-debug.c b/src/interfaces/libpq/fe-auth-oauth-debug.c index 957da5d4068..c9a82b3f78e 100644 --- a/src/interfaces/libpq/fe-auth-oauth-debug.c +++ b/src/interfaces/libpq/fe-auth-oauth-debug.c @@ -30,38 +30,38 @@ * Sets *is_unsafe to indicate if this option requires the UNSAFE: prefix. */ static bool -parse_debug_option(const char *option, oauth_debug_flags *flags, bool *is_unsafe) +parse_debug_option(const char *option, uint32 *flags, bool *is_unsafe) { *is_unsafe = false; /* Unsafe options */ if (strcmp(option, "http") == 0) { - flags->http = true; + *flags |= OAUTHDEBUG_UNSAFE_HTTP; *is_unsafe = true; return true; } else if (strcmp(option, "trace") == 0) { - flags->trace = true; + *flags |= OAUTHDEBUG_UNSAFE_TRACE; *is_unsafe = true; return true; } else if (strcmp(option, "dos-endpoint") == 0) { - flags->fast_retry = true; + *flags |= OAUTHDEBUG_UNSAFE_DOS_ENDPOINT; *is_unsafe = true; return true; } /* Safe options */ else if (strcmp(option, "call-count") == 0) { - flags->poll_counts = true; + *flags |= OAUTHDEBUG_CALL_COUNT; return true; } else if (strcmp(option, "plugin-errors") == 0) { - flags->print_plugin_errors = true; + *flags |= OAUTHDEBUG_PLUGIN_ERRORS; return true; } @@ -80,10 +80,10 @@ parse_debug_option(const char *option, oauth_debug_flags *flags, bool *is_unsafe * - An unrecognized option is specified * - An unsafe option is specified without the UNSAFE: prefix */ -oauth_debug_flags +uint32 oauth_get_debug_flags(void) { - oauth_debug_flags flags = {0}; + uint32 flags = 0; const char *env = getenv("PGOAUTHDEBUG"); char *options_str; char *option; @@ -94,14 +94,7 @@ oauth_get_debug_flags(void) return flags; if (strcmp(env, "UNSAFE") == 0) - { - flags.http = true; - flags.trace = true; - flags.fast_retry = true; - flags.poll_counts = true; - flags.print_plugin_errors = true; - return flags; - } + return OAUTHDEBUG_UNSAFE_ALL; if (strncmp(env, "UNSAFE:", 7) == 0) { diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c index 5f5900a9ae7..6f7ec3a129e 100644 --- a/src/interfaces/libpq/fe-auth-oauth.c +++ b/src/interfaces/libpq/fe-auth-oauth.c @@ -389,7 +389,7 @@ issuer_from_well_known_uri(PGconn *conn, const char *wkuri) authority_start = wkuri + strlen(HTTPS_SCHEME); if (!authority_start - && oauth_get_debug_flags().http + && (oauth_get_debug_flags() & OAUTHDEBUG_UNSAFE_HTTP) && pg_strncasecmp(wkuri, HTTP_SCHEME, strlen(HTTP_SCHEME)) == 0) { /* Allow http:// for testing only. */ @@ -900,7 +900,7 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *re * * Note that POSIX dlerror() isn't guaranteed to be threadsafe. */ - if (oauth_get_debug_flags().print_plugin_errors) + if (oauth_get_debug_flags() & OAUTHDEBUG_PLUGIN_ERRORS) fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror()); return 0; @@ -922,7 +922,7 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *re * cause is still locked behind PGOAUTHDEBUG due to the dlerror() * threadsafety issue. */ - if (oauth_get_debug_flags().print_plugin_errors) + if (oauth_get_debug_flags() & OAUTHDEBUG_PLUGIN_ERRORS) fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror()); dlclose(state->flow_module); -- 2.34.1