Subject: Re: [PoC] Federated Authn/z with OAUTHBEARER

From: Shlok Kyal <[email-masked]>
Date: Fri, 3 Nov 2023 17:58:28 +0530

Hi,

On Fri, 3 Nov 2023 at 17:14, Jacob Champion <[email-masked]> wrote:
>
> v12 implements a first draft of a client hook, so applications can
> replace either the device prompt or the entire OAuth flow. (Andrey and
> Mahendrakar: hopefully this is close to what you need.) It also cleans
> up some of the JSON tech debt.

I went through CFbot and found that build is failing, links:

https://cirrus-ci.com/task/6061898244816896
https://cirrus-ci.com/task/6624848198238208
https://cirrus-ci.com/task/5217473314684928
https://cirrus-ci.com/task/6343373221527552

Just want to make sure you are aware of these failures.

Thanks,
Shlok Kumar Kyal

---

From: Jacob Champion <[email-masked]>
Date: Fri, 3 Nov 2023 16:48:29 -0700

> Just want to make sure you are aware of these failures.

Thanks for the nudge! Looks like I need to reconcile with the changes
to JsonLexContext in 1c99cde2. I should be able to get to that next
week; in the meantime I'll mark it Waiting on Author.

--Jacob

---

From: Jacob Champion <[email-masked]>
Date: Wed, 8 Nov 2023 11:00:18 -0800

> week; in the meantime I'll mark it Waiting on Author.

v13 rebases over latest. The JsonLexContext changes have simplified
0001 quite a bit, and there's probably a bit more minimization that
could be done.

Unfortunately the configure/Makefile build of libpq now seems to be
pulling in an `exit()` dependency in a way that Meson does not. (Or
maybe Meson isn't checking?) I still need to investigate that
difference and fix it, so I recommend Meson if you're looking to
test-drive a build.

Thanks,
--Jacob
1:  e80c124c3d ! 1:  1e17a1059f common/jsonapi: support FRONTEND clients
    @@ Commit message
         memory owned by the JsonLexContext, so clients don't need to worry about
         freeing it.
     
    -    For convenience, the backend now has destroyJsonLexContext() to mirror
    -    other create/destroy APIs. The frontend has init/term versions of the
    -    API to handle stack-allocated JsonLexContexts.
    -
         We can now partially revert b44669b2ca, now that json_errdetail() works
         correctly.
     
    - ## src/backend/utils/adt/jsonfuncs.c ##
    -@@ src/backend/utils/adt/jsonfuncs.c: json_object_keys(PG_FUNCTION_ARGS)
    - 		pg_parse_json_or_ereport(lex, sem);
    - 		/* keys are now in state->result */
    - 
    --		pfree(lex->strval->data);
    --		pfree(lex->strval);
    --		pfree(lex);
    -+		destroyJsonLexContext(lex);
    - 		pfree(sem);
    - 
    - 		MemoryContextSwitchTo(oldcontext);
    -
      ## src/bin/pg_verifybackup/parse_manifest.c ##
    -@@ src/bin/pg_verifybackup/parse_manifest.c: void
    - json_parse_manifest(JsonManifestParseContext *context, char *buffer,
    - 					size_t size)
    - {
    --	JsonLexContext *lex;
    -+	JsonLexContext lex = {0};
    - 	JsonParseErrorType json_error;
    - 	JsonSemAction sem;
    - 	JsonManifestParseState parse;
    -@@ src/bin/pg_verifybackup/parse_manifest.c: json_parse_manifest(JsonManifestParseContext *context, char *buffer,
    - 	parse.state = JM_EXPECT_TOPLEVEL_START;
    - 	parse.saw_version_field = false;
    - 
    --	/* Create a JSON lexing context. */
    --	lex = makeJsonLexContextCstringLen(buffer, size, PG_UTF8, true);
    -+	/* Initialize a JSON lexing context. */
    -+	initJsonLexContextCstringLen(&lex, buffer, size, PG_UTF8, true);
    - 
    - 	/* Set up semantic actions. */
    - 	sem.semstate = &parse;
     @@ src/bin/pg_verifybackup/parse_manifest.c: json_parse_manifest(JsonManifestParseContext *context, char *buffer,
    - 	sem.scalar = json_manifest_scalar;
    - 
      	/* Run the actual JSON parser. */
    --	json_error = pg_parse_json(lex, &sem);
    -+	json_error = pg_parse_json(&lex, &sem);
    + 	json_error = pg_parse_json(lex, &sem);
      	if (json_error != JSON_SUCCESS)
     -		json_manifest_parse_failure(context, "parsing failed");
    -+		json_manifest_parse_failure(context, json_errdetail(json_error, &lex));
    ++		json_manifest_parse_failure(context, json_errdetail(json_error, lex));
      	if (parse.state != JM_EXPECT_EOF)
      		json_manifest_parse_failure(context, "manifest ended unexpectedly");
      
    - 	/* Verify the manifest checksum. */
    - 	verify_manifest_checksum(&parse, buffer, size);
    -+
    -+	/* Clean up. */
    -+	termJsonLexContext(&lex);
    - }
    - 
    - /*
     
      ## src/bin/pg_verifybackup/t/005_bad_manifest.pl ##
     @@ src/bin/pg_verifybackup/t/005_bad_manifest.pl: use Test::More;
    @@ src/common/jsonapi.c
      /*
       * The context of the parser is maintained by the recursive descent
       * mechanism, but is passed explicitly to the error reporting routine
    -@@ src/common/jsonapi.c: IsValidJsonNumber(const char *str, int len)
    - 	return (!numeric_error) && (total_len == dummy_lex.input_length);
    - }
    - 
    -+#ifndef FRONTEND
    -+
    - /*
    -  * makeJsonLexContextCstringLen
    -  *
    -- * lex constructor, with or without StringInfo object for de-escaped lexemes.
    -+ * lex constructor, with or without a string object for de-escaped lexemes.
    -  *
    -  * Without is better as it makes the processing faster, so only make one
    -  * if really required.
    -@@ src/common/jsonapi.c: makeJsonLexContextCstringLen(char *json, int len, int encoding, bool need_escape
    - {
    - 	JsonLexContext *lex = palloc0(sizeof(JsonLexContext));
    - 
    -+	initJsonLexContextCstringLen(lex, json, len, encoding, need_escapes);
    -+
    -+	return lex;
    -+}
    -+
    -+void
    -+destroyJsonLexContext(JsonLexContext *lex)
    -+{
    -+	termJsonLexContext(lex);
    -+	pfree(lex);
    -+}
    -+
    -+#endif /* !FRONTEND */
    -+
    -+void
    -+initJsonLexContextCstringLen(JsonLexContext *lex, char *json, int len, int encoding, bool need_escapes)
    -+{
    - 	lex->input = lex->token_terminator = lex->line_start = json;
    - 	lex->line_number = 1;
    - 	lex->input_length = len;
    +@@ src/common/jsonapi.c: makeJsonLexContextCstringLen(JsonLexContext *lex, char *json,
      	lex->input_encoding = encoding;
    --	if (need_escapes)
    + 	if (need_escapes)
    + 	{
     -		lex->strval = makeStringInfo();
    --	return lex;
    -+	lex->parse_strval = need_escapes;
    -+	if (lex->parse_strval)
    -+	{
     +		/*
     +		 * This call can fail in FRONTEND code. We defer error handling to time
    -+		 * of use (json_lex_string()) since there's no way to signal failure
    -+		 * here, and we might not need to parse any strings anyway.
    ++		 * of use (json_lex_string()) since we might not need to parse any
    ++		 * strings anyway.
     +		 */
     +		lex->strval = createStrVal();
    -+	}
    + 		lex->flags |= JSONLEX_FREE_STRVAL;
    ++		lex->parse_strval = true;
    + 	}
     +	lex->errormsg = NULL;
    -+}
    -+
    -+void
    -+termJsonLexContext(JsonLexContext *lex)
    -+{
    + 
    + 	return lex;
    + }
    +@@ src/common/jsonapi.c: makeJsonLexContextCstringLen(JsonLexContext *lex, char *json,
    + void
    + freeJsonLexContext(JsonLexContext *lex)
    + {
     +	static const JsonLexContext empty = {0};
     +
    -+	if (lex->strval)
    -+	{
    + 	if (lex->flags & JSONLEX_FREE_STRVAL)
    + 	{
     +#ifdef FRONTEND
     +		destroyPQExpBuffer(lex->strval);
     +#else
    -+		pfree(lex->strval->data);
    -+		pfree(lex->strval);
    + 		pfree(lex->strval->data);
    + 		pfree(lex->strval);
     +#endif
     +	}
    -+
     +	if (lex->errormsg)
     +	{
     +#ifdef FRONTEND
    @@ src/common/jsonapi.c: makeJsonLexContextCstringLen(char *json, int len, int enco
     +		pfree(lex->errormsg->data);
     +		pfree(lex->errormsg);
     +#endif
    -+	}
    -+
    -+	*lex = empty;
    + 	}
    + 	if (lex->flags & JSONLEX_FREE_STRUCT)
    + 		pfree(lex);
    ++	else
    ++		*lex = empty;
      }
      
      /*
    @@ src/common/jsonapi.c: json_errdetail(JsonParseErrorType error, JsonLexContext *l
     +	return lex->errormsg->data;
     +}
     
    + ## src/common/meson.build ##
    +@@ src/common/meson.build: common_sources_frontend_static += files(
    + # least cryptohash_openssl.c, hmac_openssl.c depend on it.
    + # controldata_utils.c depends on wait_event_types_h. That's arguably a
    + # layering violation, but ...
    ++#
    ++# XXX Frontend builds need libpq's pqexpbuffer.h, so adjust the include paths
    ++# appropriately. This seems completely broken.
    + pgcommon = {}
    + pgcommon_variants = {
    +   '_srv': internal_lib_args + {
    ++    'include_directories': include_directories('.'),
    +     'sources': common_sources + [lwlocknames_h] + [wait_event_types_h],
    +     'dependencies': [backend_common_code],
    +   },
    +   '': default_lib_args + {
    ++    'include_directories': include_directories('../interfaces/libpq', '.'),
    +     'sources': common_sources_frontend_static,
    +     'dependencies': [frontend_common_code],
    +     # Files in libpgcommon.a should use/export the "xxx_private" versions
    +@@ src/common/meson.build: pgcommon_variants = {
    +   },
    +   '_shlib': default_lib_args + {
    +     'pic': true,
    ++    'include_directories': include_directories('../interfaces/libpq', '.'),
    +     'sources': common_sources_frontend_shlib,
    +     'dependencies': [frontend_common_code],
    +   },
    +@@ src/common/meson.build: foreach name, opts : pgcommon_variants
    +     c_args = opts.get('c_args', []) + common_cflags[cflagname]
    +     cflag_libs += static_library('libpgcommon@0@_@1@'.format(name, cflagname),
    +       c_pch: pch_c_h,
    +-      include_directories: include_directories('.'),
    +       kwargs: opts + {
    +         'sources': sources,
    +         'c_args': c_args,
    +@@ src/common/meson.build: foreach name, opts : pgcommon_variants
    +   lib = static_library('libpgcommon@0@'.format(name),
    +       link_with: cflag_libs,
    +       c_pch: pch_c_h,
    +-      include_directories: include_directories('.'),
    +       kwargs: opts + {
    +         'dependencies': opts['dependencies'] + [ssl],
    +       }
    +
      ## src/include/common/jsonapi.h ##
     @@
      #ifndef JSONAPI_H
    @@ src/include/common/jsonapi.h: typedef enum JsonParseErrorType
      	JSON_UNICODE_ESCAPE_FORMAT,
      	JSON_UNICODE_HIGH_ESCAPE,
     @@ src/include/common/jsonapi.h: typedef enum JsonParseErrorType
    - 	JSON_SEM_ACTION_FAILED		/* error should already be reported */
    + 	JSON_SEM_ACTION_FAILED,		/* error should already be reported */
      } JsonParseErrorType;
      
     +/*
    @@ src/include/common/jsonapi.h: typedef enum JsonParseErrorType
      /*
       * All the fields in this structure should be treated as read-only.
     @@ src/include/common/jsonapi.h: typedef struct JsonLexContext
    - 	int			lex_level;
    + 	bits32		flags;
      	int			line_number;	/* line number, starting from 1 */
      	char	   *line_start;		/* where that line starts within input */
     -	StringInfo	strval;
    @@ src/include/common/jsonapi.h: typedef struct JsonLexContext
      } JsonLexContext;
      
      typedef JsonParseErrorType (*json_struct_action) (void *state);
    -@@ src/include/common/jsonapi.h: extern PGDLLIMPORT JsonSemAction nullSemAction;
    -  */
    - extern JsonParseErrorType json_count_array_elements(JsonLexContext *lex,
    - 													int *elements);
    -+#ifndef FRONTEND
    - 
    - /*
    -- * constructor for JsonLexContext, with or without strval element.
    -+ * allocating constructor for JsonLexContext, with or without strval element.
    -  * If supplied, the strval element will contain a de-escaped version of
    -  * the lexeme. However, doing this imposes a performance penalty, so
    -  * it should be avoided if the de-escaped lexeme is not required.
    -@@ src/include/common/jsonapi.h: extern JsonLexContext *makeJsonLexContextCstringLen(char *json,
    - 													int encoding,
    - 													bool need_escapes);
    - 
    -+/*
    -+ * Counterpart to makeJsonLexContextCstringLen(): clears and deallocates lex.
    -+ * The context pointer should not be used after this call.
    -+ */
    -+extern void destroyJsonLexContext(JsonLexContext *lex);
    -+
    -+#endif /* !FRONTEND */
    -+
    -+/*
    -+ * stack constructor for JsonLexContext, with or without strval element.
    -+ * If supplied, the strval element will contain a de-escaped version of
    -+ * the lexeme. However, doing this imposes a performance penalty, so
    -+ * it should be avoided if the de-escaped lexeme is not required.
    -+ */
    -+extern void initJsonLexContextCstringLen(JsonLexContext *lex,
    -+										 char *json,
    -+										 int len,
    -+										 int encoding,
    -+										 bool need_escapes);
    -+
    -+/*
    -+ * Counterpart to initJsonLexContextCstringLen(): clears the contents of lex,
    -+ * but does not deallocate lex itself.
    -+ */
    -+extern void termJsonLexContext(JsonLexContext *lex);
    -+
    - /* lex one token */
    - extern JsonParseErrorType json_lex(JsonLexContext *lex);
    - 
2:  02eea9ffe0 ! 2:  e941ba5807 libpq: add OAUTHBEARER SASL mechanism
    @@ src/Makefile.global.in: with_ldap	= @with_ldap@
      with_uuid	= @with_uuid@
      with_zlib	= @with_zlib@
     
    - ## src/common/meson.build ##
    -@@ src/common/meson.build: common_sources_frontend_static += files(
    - # least cryptohash_openssl.c, hmac_openssl.c depend on it.
    - # controldata_utils.c depends on wait_event_types_h. That's arguably a
    - # layering violation, but ...
    -+#
    -+# XXX Frontend builds need libpq's pqexpbuffer.h, so adjust the include paths
    -+# appropriately. This seems completely broken.
    - pgcommon = {}
    - pgcommon_variants = {
    -   '_srv': internal_lib_args + {
    -+    'include_directories': include_directories('.'),
    -     'sources': common_sources + [lwlocknames_h] + [wait_event_types_h],
    -     'dependencies': [backend_common_code],
    -   },
    -   '': default_lib_args + {
    -+    'include_directories': include_directories('../interfaces/libpq', '.'),
    -     'sources': common_sources_frontend_static,
    -     'dependencies': [frontend_common_code],
    -   },
    -   '_shlib': default_lib_args + {
    -     'pic': true,
    -+    'include_directories': include_directories('../interfaces/libpq', '.'),
    -     'sources': common_sources_frontend_shlib,
    -     'dependencies': [frontend_common_code],
    -   },
    -@@ src/common/meson.build: foreach name, opts : pgcommon_variants
    -     c_args = opts.get('c_args', []) + common_cflags[cflagname]
    -     cflag_libs += static_library('libpgcommon@0@_@1@'.format(name, cflagname),
    -       c_pch: pch_c_h,
    --      include_directories: include_directories('.'),
    -       kwargs: opts + {
    -         'sources': sources,
    -         'c_args': c_args,
    -@@ src/common/meson.build: foreach name, opts : pgcommon_variants
    -   lib = static_library('libpgcommon@0@'.format(name),
    -       link_with: cflag_libs,
    -       c_pch: pch_c_h,
    --      include_directories: include_directories('.'),
    -       kwargs: opts + {
    -         'dependencies': opts['dependencies'] + [ssl],
    -       }
    -
      ## src/include/common/oauth-common.h (new) ##
     @@
     +/*-------------------------------------------------------------------------
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +		goto cleanup;
     +	}
     +
    -+	initJsonLexContextCstringLen(&lex, resp->data, resp->len, PG_UTF8, true);
    ++	makeJsonLexContextCstringLen(&lex, resp->data, resp->len, PG_UTF8, true);
     +
     +	ctx.errbuf = &actx->errbuf;
     +	ctx.fields = fields;
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	success = true;
     +
     +cleanup:
    -+	termJsonLexContext(&lex);
    ++	freeJsonLexContext(&lex);
     +	return success;
     +}
     +
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +		return false;
     +	}
     +
    -+	initJsonLexContextCstringLen(&lex, msg, msglen, PG_UTF8, true);
    ++	makeJsonLexContextCstringLen(&lex, msg, msglen, PG_UTF8, true);
     +
     +	initPQExpBuffer(&ctx.errbuf);
     +	sem.semstate = &ctx;
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +
     +	/* Don't need the error buffer or the JSON lexer anymore. */
     +	termPQExpBuffer(&ctx.errbuf);
    -+	termJsonLexContext(&lex);
    ++	freeJsonLexContext(&lex);
     +
     +	if (errmsg)
     +		return false;
3:  b3a731b695 ! 3:  37eaa5ceb6 backend: add OAUTHBEARER SASL mechanism
    @@ src/include/libpq/auth.h: extern PGDLLIMPORT bool pg_gss_accept_delegation;
     
      ## src/include/libpq/hba.h ##
     @@ src/include/libpq/hba.h: typedef enum UserAuth
    - 	uaLDAP,
      	uaCert,
      	uaRADIUS,
    --	uaPeer
    + 	uaPeer,
     -#define USER_AUTH_LAST uaPeer	/* Must be last value of this enum */
    -+	uaPeer,
    -+	uaOAuth
    ++	uaOAuth,
     +#define USER_AUTH_LAST uaOAuth	/* Must be last value of this enum */
      } UserAuth;
      
4:  b2a570114e = 4:  7177943f0c Add pytest suite for OAuth
5:  48cd916bfe = 5:  bea695c317 squash! Add pytest suite for OAuth

---

From: Andrey Chudnovsky <[email-masked]>
Date: Thu, 9 Nov 2023 17:42:52 -0800

Hi Jacob,

Wanted to follow up on one of the topics discussed here in the past:
Do you plan to support adding an extension hook to validate the token?

It would allow a more efficient integration, then spinning a separate
process.

Thanks!
Andrey.

---

From: Jacob Champion <[email-masked]>
Date: Wed, 15 Nov 2023 12:20:56 -0800

On Thu, Nov 9, 2023 at 5:43 PM Andrey Chudnovsky <[email-masked]> wrote:
> It would allow a more efficient integration, then spinning a separate process.

I think an API in the style of archive modules might probably be a
good way to go, yeah.

It's probably not very high on the list of priorities, though, since
the inputs and outputs are going to "look" the same whether you're
inside or outside of the server process. The client side is going to
need the bulk of the work/testing/validation. Speaking of which -- how
is the current PQauthDataHook design doing when paired with MS AAD
(er, Entra now I guess)? I haven't had an Azure test bed available for
a while.

Thanks,
--Jacob

---

From: Daniel Gustafsson <[email-masked]>
Date: Tue, 5 Dec 2023 10:43:48 +0100

> On 8 Nov 2023, at 20:00, Jacob Champion <[email-masked]> wrote:

> pulling in an `exit()` dependency in a way that Meson does not.

I believe this comes from the libcurl and specifically the ntlm_wb support
which is often enabled in system and package manager provided installations.
There isn't really a fix here apart from requiring a libcurl not compiled with
ntlm_wb support, or adding an exception to the exit() check in the Makefile.

Bringing this up with other curl developers to see if it could be fixed we
instead decided to deprecate the whole module as it's quirky and not used much.
This won't help with existing installations but at least it will be deprecated
and removed by the time v17 ships, so gating on a version shipped after its
removal will avoid it.

https://github.com/curl/curl/commit/04540f69cfd4bf16e80e7c190b645f1baf505a84

> (Or maybe Meson isn't checking?) I still need to investigate that

There is no corresponding check in the Meson build, which seems like a TODO.

--
Daniel Gustafsson

---

From: Jacob Champion <[email-masked]>
Date: Tue, 9 Jan 2024 10:48:55 -0800

> https://github.com/curl/curl/commit/04540f69cfd4bf16e80e7c190b645f1baf505a84

Ooh, thank you for looking into that and fixing it!

> There is no corresponding check in the Meson build, which seems like a TODO.

Okay, I'll look into that too when I get time.

Thanks,
--Jacob

---

From: Jacob Champion <[email-masked]>
Date: Tue, 20 Feb 2024 17:00:28 -0800

Hi all,

v14 rebases over latest and fixes a warning when assertions are
disabled. 0006 is temporary and hacks past a couple of issues I have
not yet root caused -- one of which makes me wonder if 0001 needs to
be considered alongside the recent pg_combinebackup and incremental
JSON work...?

--Jacob
1:  e7f87668ab ! 1:  b6e8358f44 common/jsonapi: support FRONTEND clients
    @@ Commit message
         We can now partially revert b44669b2ca, now that json_errdetail() works
         correctly.
     
    - ## src/bin/pg_verifybackup/parse_manifest.c ##
    -@@ src/bin/pg_verifybackup/parse_manifest.c: json_parse_manifest(JsonManifestParseContext *context, char *buffer,
    - 	/* Run the actual JSON parser. */
    - 	json_error = pg_parse_json(lex, &sem);
    - 	if (json_error != JSON_SUCCESS)
    --		json_manifest_parse_failure(context, "parsing failed");
    -+		json_manifest_parse_failure(context, json_errdetail(json_error, lex));
    - 	if (parse.state != JM_EXPECT_EOF)
    - 		json_manifest_parse_failure(context, "manifest ended unexpectedly");
    - 
    -
      ## src/bin/pg_verifybackup/t/005_bad_manifest.pl ##
     @@ src/bin/pg_verifybackup/t/005_bad_manifest.pl: use Test::More;
      my $tempdir = PostgreSQL::Test::Utils::tempdir;
    @@ src/common/Makefile: override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
     +override CPPFLAGS := -DFRONTEND -I. -I$(top_srcdir)/src/common -I$(libpq_srcdir) $(CPPFLAGS)
      LIBS += $(PTHREAD_LIBS)
      
    - # If you add objects here, see also src/tools/msvc/Mkvcbuild.pm
    + OBJS_COMMON = \
     
      ## src/common/jsonapi.c ##
     @@
    @@ src/common/meson.build: foreach name, opts : pgcommon_variants
              'dependencies': opts['dependencies'] + [ssl],
            }
     
    + ## src/common/parse_manifest.c ##
    +@@ src/common/parse_manifest.c: json_parse_manifest(JsonManifestParseContext *context, char *buffer,
    + 	/* Run the actual JSON parser. */
    + 	json_error = pg_parse_json(lex, &sem);
    + 	if (json_error != JSON_SUCCESS)
    +-		json_manifest_parse_failure(context, "parsing failed");
    ++		json_manifest_parse_failure(context, json_errdetail(json_error, lex));
    + 	if (parse.state != JM_EXPECT_EOF)
    + 		json_manifest_parse_failure(context, "manifest ended unexpectedly");
    + 
    +
      ## src/include/common/jsonapi.h ##
     @@
      #ifndef JSONAPI_H
2:  0ab79a168f ! 2:  5fa08a8033 libpq: add OAUTHBEARER SASL mechanism
    @@ Commit message
         - fix libcurl initialization thread-safety
         - harden the libcurl flow implementation
         - figure out pgsocket/int difference on Windows
    +    - fix intermittent failure in the cleanup callback tests (race
    +      condition?)
         - ...and more.
     
      ## configure ##
    @@ src/interfaces/libpq/Makefile: endif
      endif
     
      ## src/interfaces/libpq/exports.txt ##
    -@@ src/interfaces/libpq/exports.txt: PQclosePrepared           188
    - PQclosePortal             189
    - PQsendClosePrepared       190
    +@@ src/interfaces/libpq/exports.txt: PQsendClosePrepared       190
      PQsendClosePortal         191
    -+PQsetAuthDataHook         192
    -+PQgetAuthDataHook         193
    -+PQdefaultAuthDataHook     194
    + PQchangePassword          192
    + PQsendPipelineSync        193
    ++PQsetAuthDataHook         194
    ++PQgetAuthDataHook         195
    ++PQdefaultAuthDataHook     196
     
      ## src/interfaces/libpq/fe-auth-oauth-curl.c (new) ##
     @@
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	 */
     +	cnt = sscanf(interval_str, "%f", &parsed);
     +
    -+	Assert(cnt == 1); /* otherwise the lexer screwed up */
    ++	if (cnt != 1)
    ++	{
    ++		/*
    ++		 * Either the lexer screwed up or our assumption above isn't true, and
    ++		 * either way a developer needs to take a look.
    ++		 */
    ++		Assert(cnt == 1);
    ++		return 1; /* don't fall through in release builds */
    ++	}
    ++
     +	parsed = ceilf(parsed);
     +
     +	if (parsed < 1)
    @@ src/interfaces/libpq/fe-auth.c: pg_fe_sendauth(AuthRequest areq, int payloadlen,
      			{
      				/* Use this message if pg_SASL_continue didn't supply one */
      				if (conn->errorMessage.len == oldmsglen)
    -@@ src/interfaces/libpq/fe-auth.c: PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
    - 
    - 	return crypt_pwd;
    +@@ src/interfaces/libpq/fe-auth.c: PQchangePassword(PGconn *conn, const char *user, const char *passwd)
    + 		}
    + 	}
      }
     +
     +PQauthDataHook_type PQauthDataHook = PQdefaultAuthDataHook;
    @@ src/interfaces/libpq/fe-connect.c: keep_going:						/* We will come back to here
      		case CONNECTION_AUTH_OK:
      			{
      				/*
    -@@ src/interfaces/libpq/fe-connect.c: makeEmptyPGconn(void)
    +@@ src/interfaces/libpq/fe-connect.c: pqMakeEmptyPGconn(void)
      	conn->verbosity = PQERRORS_DEFAULT;
      	conn->show_context = PQSHOW_CONTEXT_ERRORS;
      	conn->sock = PGINVALID_SOCKET;
    @@ src/interfaces/libpq/libpq-fe.h: extern int	PQenv2encoding(void);
     +
      extern char *PQencryptPassword(const char *passwd, const char *user);
      extern char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, const char *algorithm);
    + extern PGresult *PQchangePassword(PGconn *conn, const char *user, const char *passwd);
      
     +typedef int (*PQauthDataHook_type) (PGAuthData type, PGconn *conn, void *data);
     +extern void	PQsetAuthDataHook(PQauthDataHook_type hook);
3:  fb0cc3f87e ! 3:  13cf3f80b8 backend: add OAUTHBEARER SASL mechanism
    @@ src/backend/libpq/hba.c: parse_hba_auth_opt(char *name, char *val, HbaLine *hbal
     
      ## src/backend/libpq/meson.build ##
     @@
    - # Copyright (c) 2022-2023, PostgreSQL Global Development Group
    + # Copyright (c) 2022-2024, PostgreSQL Global Development Group
      
      backend_sources += files(
     +  'auth-oauth.c',
4:  153347752c = 4:  83a55ba4eb Add pytest suite for OAuth
5:  8b85e542a7 ! 5:  49a3b2dfd1 squash! Add pytest suite for OAuth
    @@ .cirrus.tasks.yml: task:
        # NB: Intentionally build without -Dllvm. The freebsd image size is already
        # large enough to make VM startup slow, and even without llvm freebsd
     @@ .cirrus.tasks.yml: task:
    -         --buildtype=debug \
    -         -Dcassert=true -Duuid=bsd -Dtcl_version=tcl86 -Ddtrace=auto \
    +         -Dcassert=true -Dinjection_points=true \
    +         -Duuid=bsd -Dtcl_version=tcl86 -Ddtrace=auto \
              -DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
     +        -Doauth=curl \
              -Dextra_lib_dirs=/usr/local/lib -Dextra_include_dirs=/usr/local/include/ \
-:  ---------- > 6:  a68494323f XXX temporary patches to build and test