1:  e51f717e07c ! 1:  743b4a5c3a5 Split PGOAUTHDEBUG=UNSAFE into multiple options
    @@
      ## Metadata ##
    -Author: Zsolt Parragi <zsolt.parragi@percona.com>
    +Author: Jacob Champion <jacob.champion@enterprisedb.com>
     
      ## Commit message ##
         Split PGOAUTHDEBUG=UNSAFE into multiple options
     
    +    WIP
    +
    +    Author: Zsolt Parragi <zsolt.parragi@percona.com>
    +    Co-authored-by: Jacob Champion <jacob.champion@enterprisedb.com>
    +
      ## doc/src/sgml/libpq.sgml ##
     @@ doc/src/sgml/libpq.sgml: typedef struct
         </para>
    @@ doc/src/sgml/libpq.sgml: typedef struct
     +    debug options:
     +
     +    <programlisting>
    -+PGOAUTHDEBUG=option1,option2,...    <lineannotation>for safe options only</lineannotation>
    -+PGOAUTHDEBUG=UNSAFE:option1,option2,...    <lineannotation>when using unsafe options</lineannotation>
    -+PGOAUTHDEBUG=UNSAFE    <lineannotation>legacy format; enables all options</lineannotation>
    ++PGOAUTHDEBUG=option1,option2,...         <lineannotation>for safe options only</lineannotation>
    ++PGOAUTHDEBUG=UNSAFE:option1,option2,...  <lineannotation>when using unsafe options</lineannotation>
    ++PGOAUTHDEBUG=UNSAFE                      <lineannotation>legacy format; enables all options</lineannotation>
     +    </programlisting>
     +   </para>
      
    @@ doc/src/sgml/libpq.sgml: typedef struct
     +     </varlistentry>
     +
     +     <varlistentry>
    -+      <term><literal>fast-retry</literal> (safe)</term>
    ++      <term><literal>dos-endpoint</literal> (unsafe)</term>
     +      <listitem>
     +       <para>
     +        Permits the use of zero-second retry intervals instead of the normal
    -+        minimum of one second. This can speed up tests but may cause the client
    -+        to busy-loop and consume CPU unnecessarily.
    ++        minimum of one second. This speeds up tests, but in normal operation it
    ++        will cause the client to busy-loop, consuming CPU and network resources.
     +       </para>
     +      </listitem>
     +     </varlistentry>
     +
     +     <varlistentry>
    -+      <term><literal>poll-counts</literal> (safe)</term>
    ++      <term><literal>call-count</literal> (safe)</term>
     +      <listitem>
     +       <para>
    -+        Prints the total number of poll() calls to standard error when the
    -+        OAuth flow completes. This helps developers debug the async multiplexer
    -+        behavior.
    ++        Prints the total number of calls to the flow plugin to standard error
    ++        when the OAuth flow completes. This helps developers debug the async
    ++        callback behavior.
     +       </para>
     +      </listitem>
     +     </varlistentry>
     +
     +     <varlistentry>
    -+      <term><literal>print-plugin-errors</literal> (safe)</term>
    ++      <term><literal>plugin-errors</literal> (safe)</term>
     +      <listitem>
     +       <para>
     +        Prints plugin loading errors to standard error. This helps developers
    @@ doc/src/sgml/libpq.sgml: typedef struct
     +   </para>
     +
     +   <para>
    -+    Unsafe options (<literal>http</literal>, <literal>trace</literal>)
    -+    require the <literal>UNSAFE:</literal> prefix.
    ++    Unsafe options (<literal>http</literal>, <literal>trace</literal>,
    ++    <literal>dos-endpoint</literal>) require the <literal>UNSAFE:</literal> prefix.
     +    If unsafe options are specified without this prefix, a warning is printed
     +    to standard error and that option is ignored. Other valid options in the
    -+    list continue to work. Safe options (<literal>fast-retry</literal>,
    -+    <literal>poll-counts</literal>, <literal>print-plugin-errors</literal>) can
    -+    be used without the prefix.
    ++    list continue to work. Safe options (<literal>call-count</literal>,
    ++    <literal>plugin-errors</literal>) can be used without the prefix.
         </para>
     +
     +   <para>
    @@ doc/src/sgml/libpq.sgml: typedef struct
     +   <para>
     +    Examples:
     +    <programlisting>
    -+PGOAUTHDEBUG=fast-retry,poll-counts    <lineannotation>safe options only</lineannotation>
    -+PGOAUTHDEBUG=UNSAFE:http,trace    <lineannotation>enable HTTP and traffic logging</lineannotation>
    -+PGOAUTHDEBUG=UNSAFE:http,poll-counts    <lineannotation>mix of unsafe and safe</lineannotation>
    -+PGOAUTHDEBUG=UNSAFE    <lineannotation>legacy; enables all options</lineannotation>
    ++PGOAUTHDEBUG=call-count              <lineannotation>safe options only</lineannotation>
    ++PGOAUTHDEBUG=UNSAFE:http,trace       <lineannotation>enable HTTP and traffic logging</lineannotation>
    ++PGOAUTHDEBUG=UNSAFE:http,call-count  <lineannotation>mix of unsafe and safe</lineannotation>
     +    </programlisting>
     +   </para>
     +
    @@ doc/src/sgml/libpq.sgml: typedef struct
          <para>
     -     Do not share the output of the OAuth flow traffic with third parties. It
     -     contains secrets that can be used to attack your clients and servers.
    -+     Never use unsafe debug options in production environments. The
    -+     <literal>trace</literal> option in particular exposes secrets that can be
    -+     used to attack your clients and servers. Do not share the output with third
    -+     parties.
    ++     Never use unsafe debug options in production environments. They expose
    ++     secrets and behaviors that can be used to attack your clients and servers.
    ++     Do not share <literal>trace</literal> output with third parties.
          </para>
         </warning>
        </sect2>
     
    - ## src/interfaces/libpq-oauth/meson.build ##
    -@@ src/interfaces/libpq-oauth/meson.build: endif
    - 
    - libpq_oauth_sources = files(
    -   'oauth-curl.c',
    -+  '../libpq/fe-auth-oauth-debug.c',
    - )
    - 
    - # The shared library needs additional glue symbols.
    -@@ src/interfaces/libpq-oauth/meson.build: endif
    - 
    - libpq_oauth_test_deps = []
    - 
    --oauth_test_sources = files('test-oauth-curl.c') + libpq_oauth_so_sources
    -+oauth_test_sources = files(
    -+  'test-oauth-curl.c',
    -+  '../libpq/fe-auth-oauth-debug.c',
    -+) + libpq_oauth_so_sources
    - 
    - if host_system == 'windows'
    -   oauth_test_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
    -
    - ## src/interfaces/libpq/meson.build ##
    -@@
    - 
    - libpq_sources = files(
    -   'fe-auth-oauth.c',
    -+  'fe-auth-oauth-debug.c',
    -   'fe-auth-scram.c',
    -   'fe-auth.c',
    -   'fe-cancel.c',
    -
    - ## src/interfaces/libpq-oauth/Makefile ##
    -@@ src/interfaces/libpq-oauth/Makefile: override CPPFLAGS_SHLIB += -DUSE_PRIVATE_ENCODING_FUNCS
    - OBJS = \
    - 	$(WIN32RES)
    - 
    --OBJS_STATIC = oauth-curl.o
    -+OBJS_STATIC = \
    -+	oauth-curl.o \
    -+	fe-auth-oauth-debug.o
    - 
    - # The shared library needs additional glue symbols.
    - OBJS_SHLIB = \
    - 	oauth-curl_shlib.o \
    - 	oauth-utils.o \
    -+	fe-auth-oauth-debug_shlib.o
    - 
    - oauth-utils.o: override CPPFLAGS += $(CPPFLAGS_SHLIB)
    - 
    -+fe-auth-oauth-debug.o: $(libpq_srcdir)/fe-auth-oauth-debug.c
    -+	$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
    -+
    -+fe-auth-oauth-debug_shlib.o: $(libpq_srcdir)/fe-auth-oauth-debug.c fe-auth-oauth-debug.o
    -+	$(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
    -+
    - # Add shlib-/stlib-specific objects.
    - $(shlib): override OBJS += $(OBJS_SHLIB)
    - $(shlib): $(OBJS_SHLIB)
    -
    - ## src/interfaces/libpq/Makefile ##
    -@@ src/interfaces/libpq/Makefile: OBJS = \
    - 	legacy-pqsignal.o \
    - 	libpq-events.o \
    - 	pqexpbuffer.o \
    --	fe-auth.o
    -+	fe-auth.o \
    -+	fe-auth-oauth-debug.o
    - 
    - # File shared across all SSL implementations supported.
    - ifneq ($(with_ssl),no)
    -
      ## 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"
    - 
     @@ src/interfaces/libpq-oauth/oauth-utils.h: typedef enum
      	PG_BOOL_NO					/* No (false) */
      } PGTernaryBool;
      
     -extern bool oauth_unsafe_debugging_enabled(void);
    -+extern oauth_debug_flags 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);
      
     
      ## src/interfaces/libpq/fe-auth-oauth.h ##
     @@ src/interfaces/libpq/fe-auth-oauth.h: typedef struct
    - 	void	   *builtin_flow;
      } fe_oauth_state;
      
    -+/*
    -+ * Debug flags for PGOAUTHDEBUG environment variable.
    -+ * Each flag controls a specific debug feature.
    -+ */
    -+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;
    -+
      extern void pqClearOAuthToken(PGconn *conn);
     -extern bool oauth_unsafe_debugging_enabled(void);
    -+extern oauth_debug_flags oauth_get_debug_flags(void);
      
      /* Mechanisms in fe-auth-oauth.c */
      extern const pg_fe_sasl_mech pg_oauth_mech;
     
    + ## src/interfaces/libpq/oauth-debug.h (new) ##
    +@@
    ++/*-------------------------------------------------------------------------
    ++ *
    ++ * oauth-debug.h
    ++ *	  Parsing logic for PGOAUTHDEBUG environment variable
    ++ *
    ++ * Both libpq and libpq-oauth need this logic, so it's packaged in a small
    ++ * header for convenience. This is not quite a standalone header, due to the
    ++ * complication introduced by libpq_gettext(); see note below.
    ++ *
    ++ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
    ++ * Portions Copyright (c) 1994, Regents of the University of California
    ++ *
    ++ * IDENTIFICATION
    ++ * 	src/interfaces/libpq/oauth-debug.h
    ++ *
    ++ *-------------------------------------------------------------------------
    ++ */
    ++
    ++#ifndef OAUTH_DEBUG_H
    ++#define OAUTH_DEBUG_H
    ++
    ++#include "postgres_fe.h"
    ++
    ++/*
    ++ * XXX libpq-oauth can't compile against libpq-int.h, so clients of this header
    ++ * need to provide the declaration of libpq_gettext() before #including it.
    ++ * Fortunately, there are only two such clients.
    ++ */
    ++/* #include "libpq-int.h" */
    ++
    ++/*
    ++ * 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.
    ++ */
    ++
    ++/* 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");
    ++
    ++/*
    ++ * Parses the PGOAUTHDEBUG environment variable and returns debug flags.
    ++ *
    ++ * Supported formats:
    ++ *   PGOAUTHDEBUG=UNSAFE              - legacy format, enables all features
    ++ *   PGOAUTHDEBUG=option1,option2     - enable safe features only
    ++ *   PGOAUTHDEBUG=UNSAFE:opt1,opt2    - enable unsafe and/or safe features
    ++ *
    ++ * Prints a warning and skips the invalid option if:
    ++ * - An unrecognized option is specified
    ++ * - An unsafe option is specified without the UNSAFE: prefix
    ++ */
    ++static uint32
    ++oauth_get_debug_flags(void)
    ++{
    ++	uint32		flags = 0;
    ++	const char *env = getenv("PGOAUTHDEBUG");
    ++	char	   *options_str;
    ++	char	   *option;
    ++	char	   *saveptr = NULL;
    ++	bool		unsafe_allowed = false;
    ++
    ++	if (!env || env[0] == '\0')
    ++		return flags;
    ++
    ++	if (strcmp(env, "UNSAFE") == 0)
    ++		return OAUTHDEBUG_UNSAFE_ALL;
    ++
    ++	if (strncmp(env, "UNSAFE:", 7) == 0)
    ++	{
    ++		unsafe_allowed = true;
    ++		env += 7;
    ++	}
    ++
    ++	options_str = strdup(env);
    ++	if (!options_str)
    ++		return flags;
    ++
    ++	option = strtok_r(options_str, ",", &saveptr);
    ++	while (option != NULL)
    ++	{
    ++		uint32		flag = 0;
    ++
    ++		if (strcmp(option, "http") == 0)
    ++			flag = OAUTHDEBUG_UNSAFE_HTTP;
    ++		else if (strcmp(option, "trace") == 0)
    ++			flag = OAUTHDEBUG_UNSAFE_TRACE;
    ++		else if (strcmp(option, "dos-endpoint") == 0)
    ++			flag = OAUTHDEBUG_UNSAFE_DOS_ENDPOINT;
    ++		else if (strcmp(option, "call-count") == 0)
    ++			flag = OAUTHDEBUG_CALL_COUNT;
    ++		else if (strcmp(option, "plugin-errors") == 0)
    ++			flag = OAUTHDEBUG_PLUGIN_ERRORS;
    ++		else
    ++			fprintf(stderr,
    ++					libpq_gettext("WARNING: unrecognized PGOAUTHDEBUG option \"%s\" (ignored)\n"),
    ++					option);
    ++
    ++		if (!unsafe_allowed && ((flag & OAUTHDEBUG_UNSAFE_MASK) != 0))
    ++		{
    ++			flag = 0;
    ++
    ++			fprintf(stderr,
    ++					libpq_gettext("WARNING: PGOAUTHDEBUG option \"%s\" is unsafe (ignored)\n"),
    ++					option);
    ++		}
    ++
    ++		flags |= flag;
    ++		option = strtok_r(NULL, ",", &saveptr);
    ++	}
    ++
    ++	free(options_str);
    ++
    ++	return flags;
    ++}
    ++
    ++#endif							/* OAUTH_DEBUG_H */
    +
      ## src/interfaces/libpq-oauth/oauth-curl.c ##
    +@@
    + 
    + #endif							/* USE_DYNAMIC_OAUTH */
    + 
    ++/*
    ++ * oauth-debug.h needs the declaration of libpq_gettext(), from one of the above
    ++ * sources.
    ++ */
    ++#include "oauth-debug.h"
    ++
    + /* 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
     @@ src/interfaces/libpq-oauth/oauth-curl.c: 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? */
     -	bool		debugging;		/* can we give unsafe developer assistance? */
    -+	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? */
      };
      
    @@ src/interfaces/libpq-oauth/oauth-curl.c: parse_interval(struct async_ctx *actx,
      
      	if (parsed < 1)
     -		return actx->debugging ? 0 : 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;
    @@ src/interfaces/libpq-oauth/oauth-curl.c: setup_curl_handles(struct async_ctx *ac
      	CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L, return false);
      
     -	if (actx->debugging)
    -+	if (actx->debug_flags.trace)
    ++	if (actx->debug_flags & OAUTHDEBUG_UNSAFE_TRACE)
      	{
      		/*
      		 * Set a callback for retrieving error information from libcurl, the
    @@ src/interfaces/libpq-oauth/oauth-curl.c: setup_curl_handles(struct async_ctx *ac
      #endif
      
     -		if (actx->debugging)
    -+		if (actx->debug_flags.http)
    ++		if (actx->debug_flags & OAUTHDEBUG_UNSAFE_HTTP)
      			protos = unsafe;
      
      		CHECK_SETOPT(actx, popt, protos, return false);
    @@ src/interfaces/libpq-oauth/oauth-curl.c: check_for_device_flow(struct async_ctx
      	 * we'll use for the flow.
      	 */
     -	if (!actx->debugging)
    -+	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)
    @@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow(PGconn *conn, stru
      	 * of calls to this function and print that at the end of the flow.
      	 */
     -	if (actx->debugging)
    -+	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)
    @@ src/interfaces/libpq-oauth/test-oauth-curl.c: init_test_actx(void)
      	actx->mux = PGINVALID_SOCKET;
      	actx->timerfd = -1;
     -	actx->debugging = true;
    -+	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);
      
     
    - ## src/interfaces/libpq/fe-auth-oauth-debug.c (new) ##
    -@@
    -+/*-------------------------------------------------------------------------
    -+ *
    -+ * fe-auth-oauth-debug.c
    -+ *	  Parsing logic for PGOAUTHDEBUG environment variable
    -+ *
    -+ * This file contains pure string parsing logic with no dependencies on
    -+ * libpq or libpq-oauth implementation details. It's compiled into both
    -+ * libraries to avoid code duplication.
    -+ *
    -+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
    -+ * Portions Copyright (c) 1994, Regents of the University of California
    -+ *
    -+ * IDENTIFICATION
    -+ * 	src/interfaces/libpq/fe-auth-oauth-debug.c
    -+ *
    -+ *-------------------------------------------------------------------------
    -+ */
    -+
    -+#include "postgres_fe.h"
    -+
    -+#include <stdio.h>
    -+#include <stdlib.h>
    -+#include <string.h>
    -+
    -+#include "fe-auth-oauth.h"
    -+
    -+/*
    -+ * Parse a single debug option from PGOAUTHDEBUG.
    -+ * Returns true if the option is recognized, false otherwise.
    -+ * 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)
    -+{
    -+	*is_unsafe = false;
    -+
    -+	/* Unsafe options */
    -+	if (strcmp(option, "http") == 0)
    -+	{
    -+		flags->http = true;
    -+		*is_unsafe = true;
    -+		return true;
    -+	}
    -+	else if (strcmp(option, "trace") == 0)
    -+	{
    -+		flags->trace = true;
    -+		*is_unsafe = true;
    -+		return true;
    -+	}
    -+	/* Safe options */
    -+	else if (strcmp(option, "fast-retry") == 0)
    -+	{
    -+		flags->fast_retry = true;
    -+		return true;
    -+	}
    -+	else if (strcmp(option, "poll-counts") == 0)
    -+	{
    -+		flags->poll_counts = true;
    -+		return true;
    -+	}
    -+	else if (strcmp(option, "print-plugin-errors") == 0)
    -+	{
    -+		flags->print_plugin_errors = true;
    -+		return true;
    -+	}
    -+
    -+	return false;
    -+}
    -+
    -+/*
    -+ * Parses the PGOAUTHDEBUG environment variable and returns debug flags.
    -+ *
    -+ * Supported formats:
    -+ *   PGOAUTHDEBUG=UNSAFE              - legacy format, enables all features
    -+ *   PGOAUTHDEBUG=option1,option2     - enable safe features only
    -+ *   PGOAUTHDEBUG=UNSAFE:opt1,opt2    - enable unsafe and/or safe features
    -+ *
    -+ * Prints a warning and skips the invalid option if:
    -+ * - An unrecognized option is specified
    -+ * - An unsafe option is specified without the UNSAFE: prefix
    -+ */
    -+oauth_debug_flags
    -+oauth_get_debug_flags(void)
    -+{
    -+	oauth_debug_flags flags = {0};
    -+	const char *env = getenv("PGOAUTHDEBUG");
    -+	char	   *options_str;
    -+	char	   *option;
    -+	char	   *saveptr = NULL;
    -+	bool		unsafe_prefix = false;
    -+
    -+	if (!env || env[0] == '\0')
    -+		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;
    -+	}
    -+
    -+	if (strncmp(env, "UNSAFE:", 7) == 0)
    -+	{
    -+		unsafe_prefix = true;
    -+		env += 7;
    -+	}
    -+
    -+	options_str = strdup(env);
    -+	if (!options_str)
    -+		return flags;
    -+
    -+	option = strtok_r(options_str, ",", &saveptr);
    -+	while (option != NULL)
    -+	{
    -+		bool		is_unsafe;
    -+
    -+		if (!parse_debug_option(option, &flags, &is_unsafe))
    -+		{
    -+			fprintf(stderr,
    -+					"WARNING: PGOAUTHDEBUG: unrecognized debug option \"%s\" (ignored)\n",
    -+					option);
    -+		}
    -+		else if (is_unsafe && !unsafe_prefix)
    -+		{
    -+			fprintf(stderr,
    -+					"WARNING: PGOAUTHDEBUG: unsafe option \"%s\" requires UNSAFE: prefix (ignored)\n"
    -+					"Use: PGOAUTHDEBUG=UNSAFE:%s\n",
    -+					option, option);
    -+		}
    -+
    -+		option = strtok_r(NULL, ",", &saveptr);
    -+	}
    -+
    -+	free(options_str);
    -+
    -+	return flags;
    -+}
    -
      ## src/interfaces/libpq/fe-auth-oauth.c ##
    +@@
    + #include "fe-auth.h"
    + #include "fe-auth-oauth.h"
    + #include "mb/pg_wchar.h"
    ++#include "oauth-debug.h"
    + #include "pg_config_paths.h"
    + #include "utils/memdebug.h"
    + 
     @@ src/interfaces/libpq/fe-auth-oauth.c: issuer_from_well_known_uri(PGconn *conn, const char *wkuri)
      		authority_start = wkuri + strlen(HTTPS_SCHEME);
      
      	if (!authority_start
     -		&& oauth_unsafe_debugging_enabled()
    -+		&& 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. */
    @@ src/interfaces/libpq/fe-auth-oauth.c: use_builtin_flow(PGconn *conn, fe_oauth_st
      		 * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
      		 */
     -		if (oauth_unsafe_debugging_enabled())
    -+		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;
    @@ src/interfaces/libpq/fe-auth-oauth.c: use_builtin_flow(PGconn *conn, fe_oauth_st
      		 * threadsafety issue.
      		 */
     -		if (oauth_unsafe_debugging_enabled())
    -+		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->builtin_flow);
    + 		dlclose(state->flow_module);
     @@ src/interfaces/libpq/fe-auth-oauth.c: pqClearOAuthToken(PGconn *conn)
      	conn->oauth_token = NULL;
      }
    @@ src/interfaces/libpq/fe-auth-oauth.c: pqClearOAuthToken(PGconn *conn)
     -
     -	return (env && strcmp(env, "UNSAFE") == 0);
     -}
    +-
    + /*
    +  * Hook v1 Poisoning
    +  *
    +
    + ## src/test/modules/oauth_validator/t/001_server.pl ##
    +@@ src/test/modules/oauth_validator/t/001_server.pl: $node->connect_fails(
    + 	  qr@OAuth discovery URI "\Q$issuer\E/.well-known/openid-configuration" must use HTTPS@
    + );
    + 
    ++{
    ++	# PGOAUTHDEBUG=http should have no effect (it needs an UNSAFE: marker).
    ++	local $ENV{PGOAUTHDEBUG} = "http";
    ++
    ++	$node->connect_fails(
    ++		"user=test dbname=postgres oauth_issuer=$issuer oauth_client_id=f02c6361-0635",
    ++		"HTTPS is required without debug mode (bad PGOAUTHDEBUG value)",
    ++		expected_stderr => qr[
    ++			^WARNING: .* \Qoption "http" is unsafe\E
    ++			.*
    ++			\QOAuth discovery URI "$issuer/.well-known/openid-configuration" must use HTTPS\E
    ++		]msx
    ++	);
    ++}
    ++
    + # Switch to HTTPS.
    + $issuer = "https://127.0.0.1:$port";
    + 
    +@@ src/test/modules/oauth_validator/t/001_server.pl: $node->connect_ok(
    + 	],
    + 	log_unlike => [qr/FATAL.*OAuth bearer authentication failed/]);
    + 
    +-# Enable PGOAUTHDEBUG for all remaining tests.
    +-$ENV{PGOAUTHDEBUG} = "UNSAFE";
    ++# Enable some debugging features for all remaining tests:
    ++# - trace, for detailed Curl logs on failure
    ++# - dos-endpoint, to speed up the three-way handshake
    ++# - call-count, for our later sanity check
    ++$ENV{PGOAUTHDEBUG} = "UNSAFE:trace,dos-endpoint,call-count";
    + 
    + # The /alternate issuer uses slightly different parameters, along with an
    + # OAuth-style discovery document.
    +
    + ## src/tools/pginclude/headerscheck ##
    +@@ src/tools/pginclude/headerscheck: do
    + 	test "$f" = src/include/catalog/syscache_ids.h && continue
    + 	test "$f" = src/include/catalog/syscache_info.h && continue
    + 
    ++	test "$f" = src/interfaces/libpq/oauth-debug.h && continue
    ++
    + 	# We can't make these Bison output files compilable standalone
    + 	# without using "%code require", which old Bison versions lack.
    + 	# parser/gram.h will be included by parser/gramparse.h anyway.
2:  933f6432f87 < -:  ----------- Add new PGOAUTHDEBUG option: issuer-mismatch
