since-v14.diff.txt

text/plain

Filename: since-v14.diff.txt
Type: text/plain
Part: 0
Message: Re: [PoC] Federated Authn/z with OAUTHBEARER
1:  b6e8358f44 ! 1:  92cf9bdcb3 common/jsonapi: support FRONTEND clients
    @@ src/common/jsonapi.c
     +#define createStrVal		createPQExpBuffer
     +#define resetStrVal			resetPQExpBuffer
     +
    -+#else /* !FRONTEND */
    ++#else							/* !FRONTEND */
     +
     +#define STRDUP(s) pstrdup(s)
     +#define ALLOC(size) palloc(size)
    @@ src/common/jsonapi.c: makeJsonLexContextCstringLen(JsonLexContext *lex, char *js
      	{
     -		lex->strval = makeStringInfo();
     +		/*
    -+		 * This call can fail in FRONTEND code. We defer error handling to time
    -+		 * of use (json_lex_string()) since we might not need to parse any
    -+		 * strings anyway.
    ++		 * This call can fail in FRONTEND code. We defer error handling to
    ++		 * time of use (json_lex_string()) since we might not need to parse
    ++		 * any strings anyway.
     +		 */
     +		lex->strval = createStrVal();
      		lex->flags |= JSONLEX_FREE_STRVAL;
    @@ src/common/jsonapi.c: json_count_array_elements(JsonLexContext *lex, int *elemen
      	 */
      	memcpy(&copylex, lex, sizeof(JsonLexContext));
     -	copylex.strval = NULL;		/* not interested in values here */
    -+	copylex.parse_strval = false;		/* not interested in values here */
    ++	copylex.parse_strval = false;	/* not interested in values here */
      	copylex.lex_level++;
      
      	count = 0;
    @@ src/common/jsonapi.c: parse_object(JsonLexContext *lex, JsonSemAction *sem)
      	JsonParseErrorType result;
      
      #ifndef FRONTEND
    ++
     +	/*
     +	 * TODO: clients need some way to put a bound on stack growth. Parse level
     +	 * limits maybe?
    @@ src/common/jsonapi.c: report_parse_error(JsonParseContext ctx, JsonLexContext *l
      char *
      json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
      {
    -+	int		toklen = lex->token_terminator - lex->token_start;
    ++	int			toklen = lex->token_terminator - lex->token_start;
     +
     +	if (error == JSON_OUT_OF_MEMORY)
     +	{
    @@ src/common/jsonapi.c: json_errdetail(JsonParseErrorType error, JsonLexContext *l
      			return _("Unicode escape values cannot be used for code point values above 007F when the encoding is not UTF8.");
      		case JSON_UNICODE_UNTRANSLATABLE:
     -			/* note: this case is only reachable in backend not frontend */
    ++
     +			/*
     +			 * note: this case is only reachable in backend not frontend.
     +			 * #ifdef it away so the frontend doesn't try to link against
    @@ src/common/jsonapi.c: json_errdetail(JsonParseErrorType error, JsonLexContext *l
     +	{
     +		/*
     +		 * We don't use a default: case, so that the compiler will warn about
    -+		 * unhandled enum values.  But this needs to be here anyway to cover the
    -+		 * possibility of an incorrect input.
    ++		 * unhandled enum values.  But this needs to be here anyway to cover
    ++		 * the possibility of an incorrect input.
     +		 */
     +		appendStrVal(lex->errormsg,
     +					 "unexpected json parse error type: %d", (int) error);
2:  5fa08a8033 ! 2:  0a58e64ade libpq: add OAUTHBEARER SASL mechanism
    @@ src/include/common/oauth-common.h (new)
     +/* Name of SASL mechanism per IANA */
     +#define OAUTHBEARER_NAME "OAUTHBEARER"
     +
    -+#endif /* OAUTH_COMMON_H */
    ++#endif							/* OAUTH_COMMON_H */
     
      ## src/include/pg_config.h.in ##
     @@
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     + */
     +struct async_ctx
     +{
    -+	OAuthStep	step;		/* where are we in the flow? */
    ++	OAuthStep	step;			/* where are we in the flow? */
     +
     +#ifdef HAVE_SYS_EPOLL_H
    -+	int			timerfd;	/* a timerfd for signaling async timeouts */
    ++	int			timerfd;		/* a timerfd for signaling async timeouts */
     +#endif
    -+	pgsocket	mux;		/* the multiplexer socket containing all descriptors
    -+							   tracked by cURL, plus the timerfd */
    -+	CURLM	   *curlm;		/* top-level multi handle for cURL operations */
    -+	CURL	   *curl;		/* the (single) easy handle for serial requests */
    -+
    -+	struct curl_slist  *headers;	/* common headers for all requests */
    -+	PQExpBufferData		work_data;	/* scratch buffer for general use (remember
    -+									   to clear out prior contents first!) */
    -+
    -+	/*
    -+	 * Since a single logical operation may stretch across multiple calls to our
    -+	 * entry point, errors have three parts:
    ++	pgsocket	mux;			/* the multiplexer socket containing all
    ++								 * descriptors tracked by cURL, plus the
    ++								 * timerfd */
    ++	CURLM	   *curlm;			/* top-level multi handle for cURL operations */
    ++	CURL	   *curl;			/* the (single) easy handle for serial
    ++								 * requests */
    ++
    ++	struct curl_slist *headers; /* common headers for all requests */
    ++	PQExpBufferData work_data;	/* scratch buffer for general use (remember to
    ++								 * clear out prior contents first!) */
    ++
    ++	/*------
    ++	 * Since a single logical operation may stretch across multiple calls to
    ++	 * our entry point, errors have three parts:
     +	 *
     +	 * - errctx:	an optional static string, describing the global operation
    -+	 * 				currently in progress. It'll be translated for you.
    ++	 *				currently in progress. It'll be translated for you.
     +	 *
     +	 * - errbuf:	contains the actual error message. Generally speaking, use
    -+	 * 				actx_error[_str] to manipulate this. This must be filled
    -+	 * 				with something useful on an error.
    ++	 *				actx_error[_str] to manipulate this. This must be filled
    ++	 *				with something useful on an error.
     +	 *
    -+	 * - curl_err:	an optional static error buffer used by cURL to put detailed
    -+	 * 				information about failures. Unfortunately untranslatable.
    ++	 * - curl_err:	an optional static error buffer used by cURL to put
    ++	 *				detailed information about failures. Unfortunately
    ++	 *				untranslatable.
     +	 *
     +	 * These pieces will be combined into a single error message looking
     +	 * something like the following, with errctx and/or curl_err omitted when
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	 *
     +	 *     connection to server ... failed: errctx: errbuf (curl_err)
     +	 */
    -+	const char	   *errctx;	/* not freed; must point to static allocation */
    -+	PQExpBufferData	errbuf;
    -+	char			curl_err[CURL_ERROR_SIZE];
    ++	const char *errctx;			/* not freed; must point to static allocation */
    ++	PQExpBufferData errbuf;
    ++	char		curl_err[CURL_ERROR_SIZE];
     +
     +	/*
     +	 * These documents need to survive over multiple calls, and are therefore
     +	 * cached directly in the async_ctx.
     +	 */
    -+	struct provider		provider;
    -+	struct device_authz	authz;
    ++	struct provider provider;
    ++	struct device_authz authz;
     +
     +	bool		user_prompted;	/* have we already sent the authz prompt? */
     +};
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +{
     +	struct async_ctx *actx = ctx;
     +
    -+	Assert(actx); /* oauth_free() shouldn't call us otherwise */
    ++	Assert(actx);				/* oauth_free() shouldn't call us otherwise */
     +
     +	/*
    -+	 * TODO: in general, none of the error cases below should ever happen if we
    -+	 * have no bugs above. But if we do hit them, surfacing those errors somehow
    -+	 * might be the only way to have a chance to debug them. What's the best way
    -+	 * to do that? Assertions? Spraying messages on stderr? Bubbling an error
    -+	 * code to the top? Appending to the connection's error message only helps
    -+	 * if the bug caused a connection failure; otherwise it'll be buried...
    ++	 * TODO: in general, none of the error cases below should ever happen if
    ++	 * we have no bugs above. But if we do hit them, surfacing those errors
    ++	 * somehow might be the only way to have a chance to debug them. What's
    ++	 * the best way to do that? Assertions? Spraying messages on stderr?
    ++	 * Bubbling an error code to the top? Appending to the connection's error
    ++	 * message only helps if the bug caused a connection failure; otherwise
    ++	 * it'll be buried...
     +	 */
     +
     +	if (actx->curlm && actx->curl)
     +	{
     +		CURLMcode	err = curl_multi_remove_handle(actx->curlm, actx->curl);
    ++
     +		if (err)
     +			libpq_append_conn_error(conn,
     +									"cURL easy handle removal failed: %s",
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	if (actx->curlm)
     +	{
     +		CURLMcode	err = curl_multi_cleanup(actx->curlm);
    ++
     +		if (err)
     +			libpq_append_conn_error(conn,
     +									"cURL multi handle cleanup failed: %s",
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     + */
     +struct json_field
     +{
    -+	const char *name;		/* name (key) of the member */
    ++	const char *name;			/* name (key) of the member */
     +
    -+	JsonTokenType type;		/* currently supports JSON_TOKEN_STRING,
    -+							 * JSON_TOKEN_NUMBER, and JSON_TOKEN_ARRAY_START */
    ++	JsonTokenType type;			/* currently supports JSON_TOKEN_STRING,
    ++								 * JSON_TOKEN_NUMBER, and
    ++								 * JSON_TOKEN_ARRAY_START */
     +	union
     +	{
    -+		char  **scalar;				/* for all scalar types */
    ++		char	  **scalar;		/* for all scalar types */
     +		struct curl_slist **array;	/* for type == JSON_TOKEN_ARRAY_START */
     +	};
     +
    -+	bool		required;	/* REQUIRED field, or just OPTIONAL? */
    ++	bool		required;		/* REQUIRED field, or just OPTIONAL? */
     +};
     +
     +/* Documentation macros for json_field.required. */
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +/* Parse state for parse_oauth_json(). */
     +struct oauth_parse
     +{
    -+	PQExpBuffer errbuf; /* detail message for JSON_SEM_ACTION_FAILED */
    -+	int			nested; /* nesting level (zero is the top) */
    ++	PQExpBuffer errbuf;			/* detail message for JSON_SEM_ACTION_FAILED */
    ++	int			nested;			/* nesting level (zero is the top) */
     +
     +	const struct json_field *fields;	/* field definition array */
     +	const struct json_field *active;	/* points inside the fields array */
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	Assert(ctx->active);
     +
     +	/*
    -+	 * At the moment, the only fields we're interested in are strings, numbers,
    -+	 * and arrays of strings.
    ++	 * At the moment, the only fields we're interested in are strings,
    ++	 * numbers, and arrays of strings.
     +	 */
     +	switch (ctx->active->type)
     +	{
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +		 * We should never start parsing a new field while a previous one is
     +		 * still active.
     +		 *
    -+		 * TODO: this code relies on assertions too much. We need to exit sanely
    -+		 * on internal logic errors, to avoid turning bugs into vulnerabilities.
    ++		 * TODO: this code relies on assertions too much. We need to exit
    ++		 * sanely on internal logic errors, to avoid turning bugs into
    ++		 * vulnerabilities.
     +		 */
     +		Assert(!ctx->active);
     +
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	if (ctx->active)
     +	{
     +		if (ctx->active->type != JSON_TOKEN_ARRAY_START
    -+			/* The arrays we care about must not have arrays as values. */
    ++		/* The arrays we care about must not have arrays as values. */
     +			|| ctx->nested > 1)
     +		{
     +			report_type_mismatch(ctx);
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	if (ctx->active)
     +	{
     +		/*
    -+		 * This assumes that no target arrays can contain other arrays, which we
    -+		 * check in the array_start callback.
    ++		 * This assumes that no target arrays can contain other arrays, which
    ++		 * we check in the array_start callback.
     +		 */
     +		Assert(ctx->nested == 2);
     +		Assert(ctx->active->type == JSON_TOKEN_ARRAY_START);
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +
     +	if (ctx->active)
     +	{
    -+		JsonTokenType	expected;
    ++		JsonTokenType expected;
     +
     +		/*
     +		 * Make sure this matches what the active field expects. Arrays must
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +			*ctx->active->scalar = token;
     +			ctx->active = NULL;
     +
    -+			return JSON_SUCCESS; /* don't free the token */
    ++			return JSON_SUCCESS;	/* don't free the token */
     +		}
    -+		else /* ctx->target_array */
    ++		else					/* ctx->target_array */
     +		{
     +			struct curl_slist *temp;
     +
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +static bool
     +parse_oauth_json(struct async_ctx *actx, const struct json_field *fields)
     +{
    -+	PQExpBuffer			resp = &actx->work_data;
    -+	char			   *content_type;
    -+	JsonLexContext		lex = {0};
    -+	JsonSemAction		sem = {0};
    -+	JsonParseErrorType	err;
    -+	struct oauth_parse	ctx = {0};
    -+	bool				success = false;
    ++	PQExpBuffer resp = &actx->work_data;
    ++	char	   *content_type;
    ++	JsonLexContext lex = {0};
    ++	JsonSemAction sem = {0};
    ++	JsonParseErrorType err;
    ++	struct oauth_parse ctx = {0};
    ++	bool		success = false;
     +
     +	/* Make sure the server thinks it's given us JSON. */
     +	CHECK_GETINFO(actx, CURLINFO_CONTENT_TYPE, &content_type);
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	if (err != JSON_SUCCESS)
     +	{
     +		/*
    -+		 * For JSON_SEM_ACTION_FAILED, we've already written the error message.
    -+		 * Other errors come directly from pg_parse_json(), already translated.
    ++		 * For JSON_SEM_ACTION_FAILED, we've already written the error
    ++		 * message. Other errors come directly from pg_parse_json(), already
    ++		 * translated.
     +		 */
     +		if (err != JSON_SEM_ACTION_FAILED)
     +			actx_error_str(actx, json_errdetail(err, &lex));
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +parse_provider(struct async_ctx *actx, struct provider *provider)
     +{
     +	struct json_field fields[] = {
    -+		{ "issuer",         JSON_TOKEN_STRING, { &provider->issuer },         REQUIRED },
    -+		{ "token_endpoint", JSON_TOKEN_STRING, { &provider->token_endpoint }, REQUIRED },
    ++		{"issuer", JSON_TOKEN_STRING, {&provider->issuer}, REQUIRED},
    ++		{"token_endpoint", JSON_TOKEN_STRING, {&provider->token_endpoint}, REQUIRED},
     +
    -+		/*
    -+		 * The following fields are technically REQUIRED, but we don't use them
    -+		 * anywhere yet:
    ++		/*----
    ++		 * The following fields are technically REQUIRED, but we don't use
    ++		 * them anywhere yet:
     +		 *
     +		 * - jwks_uri
     +		 * - response_types_supported
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +		 * - id_token_signing_alg_values_supported
     +		 */
     +
    -+		{ "device_authorization_endpoint", JSON_TOKEN_STRING,      { &provider->device_authorization_endpoint },  OPTIONAL },
    -+		{ "grant_types_supported",         JSON_TOKEN_ARRAY_START, { .array = &provider->grant_types_supported }, OPTIONAL },
    ++		{"device_authorization_endpoint", JSON_TOKEN_STRING, {&provider->device_authorization_endpoint}, OPTIONAL},
    ++		{"grant_types_supported", JSON_TOKEN_ARRAY_START, {.array = &provider->grant_types_supported}, OPTIONAL},
     +
    -+		{ 0 },
    ++		{0},
     +	};
     +
     +	return parse_oauth_json(actx, fields);
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +		 * either way a developer needs to take a look.
     +		 */
     +		Assert(cnt == 1);
    -+		return 1; /* don't fall through in release builds */
    ++		return 1;				/* don't fall through in release builds */
     +	}
     +
     +	parsed = ceilf(parsed);
     +
     +	if (parsed < 1)
    -+		return 1; /* TODO this slows down the tests considerably... */
    ++		return 1;				/* TODO this slows down the tests
    ++								 * considerably... */
     +	else if (INT_MAX <= parsed)
     +		return INT_MAX;
     +
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +parse_device_authz(struct async_ctx *actx, struct device_authz *authz)
     +{
     +	struct json_field fields[] = {
    -+		{ "device_code",      JSON_TOKEN_STRING, { &authz->device_code },      REQUIRED },
    -+		{ "user_code",        JSON_TOKEN_STRING, { &authz->user_code },        REQUIRED },
    -+		{ "verification_uri", JSON_TOKEN_STRING, { &authz->verification_uri }, REQUIRED },
    ++		{"device_code", JSON_TOKEN_STRING, {&authz->device_code}, REQUIRED},
    ++		{"user_code", JSON_TOKEN_STRING, {&authz->user_code}, REQUIRED},
    ++		{"verification_uri", JSON_TOKEN_STRING, {&authz->verification_uri}, REQUIRED},
     +
     +		/*
    -+		 * The following fields are technically REQUIRED, but we don't use them
    -+		 * anywhere yet:
    ++		 * The following fields are technically REQUIRED, but we don't use
    ++		 * them anywhere yet:
     +		 *
     +		 * - expires_in
     +		 */
     +
    -+		{ "interval", JSON_TOKEN_NUMBER, { &authz->interval_str }, OPTIONAL },
    ++		{"interval", JSON_TOKEN_NUMBER, {&authz->interval_str}, OPTIONAL},
     +
    -+		{ 0 },
    ++		{0},
     +	};
     +
     +	if (!parse_oauth_json(actx, fields))
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +{
     +	bool		result;
     +	struct json_field fields[] = {
    -+		{ "error", JSON_TOKEN_STRING, { &err->error }, REQUIRED },
    ++		{"error", JSON_TOKEN_STRING, {&err->error}, REQUIRED},
     +
    -+		{ "error_description", JSON_TOKEN_STRING, { &err->error_description }, OPTIONAL },
    ++		{"error_description", JSON_TOKEN_STRING, {&err->error_description}, OPTIONAL},
     +
    -+		{ 0 },
    ++		{0},
     +	};
     +
     +	result = parse_oauth_json(actx, fields);
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +parse_access_token(struct async_ctx *actx, struct token *tok)
     +{
     +	struct json_field fields[] = {
    -+		{ "access_token", JSON_TOKEN_STRING, { &tok->access_token }, REQUIRED },
    -+		{ "token_type",   JSON_TOKEN_STRING, { &tok->token_type },   REQUIRED },
    ++		{"access_token", JSON_TOKEN_STRING, {&tok->access_token}, REQUIRED},
    ++		{"token_type", JSON_TOKEN_STRING, {&tok->token_type}, REQUIRED},
     +
     +		/*
    -+		 * The following fields are technically REQUIRED, but we don't use them
    -+		 * anywhere yet:
    ++		 * The following fields are technically REQUIRED, but we don't use
    ++		 * them anywhere yet:
     +		 *
    -+		 * - scope (only required if different than requested -- TODO check it)
    ++		 * - scope (only required if different than requested -- TODO check)
     +		 */
     +
    -+		{ 0 },
    ++		{0},
     +	};
     +
     +	return parse_oauth_json(actx, fields);
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	{
     +		switch (op)
     +		{
    -+		case EPOLL_CTL_ADD:
    -+			actx_error(actx, "could not add to epoll set: %m");
    -+			break;
    ++			case EPOLL_CTL_ADD:
    ++				actx_error(actx, "could not add to epoll set: %m");
    ++				break;
     +
    -+		case EPOLL_CTL_DEL:
    -+			actx_error(actx, "could not delete from epoll set: %m");
    -+			break;
    ++			case EPOLL_CTL_DEL:
    ++				actx_error(actx, "could not delete from epoll set: %m");
    ++				break;
     +
    -+		default:
    -+			actx_error(actx, "could not update epoll set: %m");
    ++			default:
    ++				actx_error(actx, "could not update epoll set: %m");
     +		}
     +
     +		return -1;
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +			break;
     +
     +		case CURL_POLL_REMOVE:
    ++
     +			/*
     +			 * We don't know which of these is currently registered, perhaps
     +			 * both, so we try to remove both.  This means we need to tolerate
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	}
     +
     +	/*
    -+	 * We can't use the simple errno version of kevent, because we need to skip
    -+	 * over ENOENT while still allowing a second change to be processed.  So we
    -+	 * need a longer-form error checking loop.
    ++	 * We can't use the simple errno version of kevent, because we need to
    ++	 * skip over ENOENT while still allowing a second change to be processed.
    ++	 * So we need a longer-form error checking loop.
     +	 */
     +	for (int i = 0; i < res; ++i)
     +	{
     +		/*
     +		 * EV_RECEIPT should guarantee one EV_ERROR result for every change,
    -+		 * whether successful or not. Failed entries contain a non-zero errno in
    -+		 * the `data` field.
    ++		 * whether successful or not. Failed entries contain a non-zero errno
    ++		 * in the `data` field.
     +		 */
     +		Assert(ev_out[i].flags & EV_ERROR);
     +
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +		{
     +			switch (what)
     +			{
    -+			case CURL_POLL_REMOVE:
    -+				actx_error(actx, "could not delete from kqueue: %m");
    -+				break;
    -+			default:
    -+				actx_error(actx, "could not add to kqueue: %m");
    ++				case CURL_POLL_REMOVE:
    ++					actx_error(actx, "could not delete from kqueue: %m");
    ++					break;
    ++				default:
    ++					actx_error(actx, "could not add to kqueue: %m");
     +			}
     +			return -1;
     +		}
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	else if (timeout == 0)
     +	{
     +		/*
    -+		 * A zero timeout means cURL wants us to call back immediately.  That's
    ++		 * A zero timeout means cURL wants us to call back immediately. That's
     +		 * not technically an option for timerfd, but we can make the timeout
     +		 * ridiculously short.
     +		 *
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +		spec.it_value.tv_nsec = (timeout % 1000) * 1000000;
     +	}
     +
    -+	if (timerfd_settime(actx->timerfd, 0 /* no flags */, &spec, NULL) < 0)
    ++	if (timerfd_settime(actx->timerfd, 0 /* no flags */ , &spec, NULL) < 0)
     +	{
     +		actx_error(actx, "setting timerfd to %ld: %m", timeout);
     +		return -1;
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	}
     +
     +	/*
    -+	 * The multi handle tells us what to wait on using two callbacks. These will
    -+	 * manipulate actx->mux as needed.
    ++	 * The multi handle tells us what to wait on using two callbacks. These
    ++	 * will manipulate actx->mux as needed.
     +	 */
     +	CHECK_MSETOPT(actx, CURLMOPT_SOCKETFUNCTION, register_socket);
     +	CHECK_MSETOPT(actx, CURLMOPT_SOCKETDATA, actx);
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	CHECK_MSETOPT(actx, CURLMOPT_TIMERDATA, actx);
     +
     +	/*
    -+	 * Set up an easy handle. All of our requests are made serially, so we only
    -+	 * ever need to keep track of one.
    ++	 * Set up an easy handle. All of our requests are made serially, so we
    ++	 * only ever need to keep track of one.
     +	 */
     +	actx->curl = curl_easy_init();
     +	if (!actx->curl)
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	CHECK_SETOPT(actx, CURLOPT_WRITEDATA, stderr);
     +
     +	/*
    -+	 * Only HTTP[S] is allowed.
    -+	 * TODO: disallow HTTP without user opt-in
    ++	 * Only HTTP[S] is allowed. TODO: disallow HTTP without user opt-in
     +	 */
     +	CHECK_SETOPT(actx, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
     +
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	 * pretty strict when it comes to provider behavior, so we have to check
     +	 * what comes back anyway.)
     +	 */
    -+	actx->headers = curl_slist_append(actx->headers, "Accept:"); /* TODO: check result */
    ++	actx->headers = curl_slist_append(actx->headers, "Accept:");	/* TODO: check result */
     +	CHECK_SETOPT(actx, CURLOPT_HTTPHEADER, actx->headers);
     +
     +	return true;
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	 * Sanity check.
     +	 *
     +	 * TODO: even though this is nominally an asynchronous process, there are
    -+	 * apparently operations that can synchronously fail by this point, such as
    -+	 * connections to closed local ports. Maybe we need to let this case fall
    -+	 * through to drive_request instead, or else perform a curl_multi_info_read
    -+	 * immediately.
    ++	 * apparently operations that can synchronously fail by this point, such
    ++	 * as connections to closed local ports. Maybe we need to let this case
    ++	 * fall through to drive_request instead, or else perform a
    ++	 * curl_multi_info_read immediately.
     +	 */
     +	if (running != 1)
     +	{
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +{
     +	CURLMcode	err;
     +	int			running;
    -+	CURLMsg	   *msg;
    ++	CURLMsg    *msg;
     +	int			msgs_left;
     +	bool		done;
     +
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +		if (msg->msg != CURLMSG_DONE)
     +		{
     +			/*
    -+			 * Future cURL versions may define new message types; we don't know
    -+			 * how to handle them, so we'll ignore them.
    ++			 * Future cURL versions may define new message types; we don't
    ++			 * know how to handle them, so we'll ignore them.
     +			 */
     +			continue;
     +		}
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +{
     +	long		response_code;
     +
    -+	/*
    ++	/*----
     +	 * Now check the response. OIDC Discovery 1.0 is pretty strict:
     +	 *
    -+	 *     A successful response MUST use the 200 OK HTTP status code and return
    -+	 *     a JSON object using the application/json content type that contains a
    -+	 *     set of Claims as its members that are a subset of the Metadata values
    -+	 *     defined in Section 3.
    ++	 *     A successful response MUST use the 200 OK HTTP status code and
    ++	 *     return a JSON object using the application/json content type that
    ++	 *     contains a set of Claims as its members that are a subset of the
    ++	 *     Metadata values defined in Section 3.
     +	 *
     +	 * Compared to standard HTTP semantics, this makes life easy -- we don't
     +	 * need to worry about redirections (which would call the Issuer host
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	 */
     +	actx->errctx = "failed to parse OpenID discovery document";
     +	if (!parse_provider(actx, &actx->provider))
    -+		return false; /* error message already set */
    ++		return false;			/* error message already set */
     +
     +	/*
     +	 * Fill in any defaults for OPTIONAL/RECOMMENDED fields we care about.
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	const struct curl_slist *grant;
     +	bool		device_grant_found = false;
     +
    -+	Assert(provider->issuer); /* ensured by get_discovery_document() */
    ++	Assert(provider->issuer);	/* ensured by get_discovery_document() */
     +
    -+	/*
    -+	 * First, sanity checks for discovery contents that are OPTIONAL in the spec
    -+	 * but required for our flow:
    ++	/*------
    ++	 * First, sanity checks for discovery contents that are OPTIONAL in the
    ++	 * spec but required for our flow:
     +	 * - the issuer must support the device_code grant
    -+	 * - the issuer must have actually given us a device_authorization_endpoint
    ++	 * - the issuer must have actually given us a
    ++	 *   device_authorization_endpoint
     +	 */
     +
     +	grant = provider->grant_types_supported;
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	const char *device_authz_uri = actx->provider.device_authorization_endpoint;
     +	PQExpBuffer work_buffer = &actx->work_data;
     +
    -+	Assert(conn->oauth_client_id); /* ensured by get_auth_token() */
    -+	Assert(device_authz_uri); /* ensured by check_for_device_flow() */
    ++	Assert(conn->oauth_client_id);	/* ensured by get_auth_token() */
    ++	Assert(device_authz_uri);	/* ensured by check_for_device_flow() */
     +
     +	/* Construct our request body. TODO: url-encode */
     +	resetPQExpBuffer(work_buffer);
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +
     +	if (conn->oauth_client_secret)
     +	{
    -+		/*
    ++		/*----
     +		 * Use HTTP Basic auth to send the password. Per RFC 6749, Sec. 2.3.1,
     +		 *
    -+		 *   Including the client credentials in the request-body using the two
    -+		 *   parameters is NOT RECOMMENDED and SHOULD be limited to clients
    -+		 *   unable to directly utilize the HTTP Basic authentication scheme (or
    -+		 *   other password-based HTTP authentication schemes).
    ++		 *   Including the client credentials in the request-body using the
    ++		 *   two parameters is NOT RECOMMENDED and SHOULD be limited to
    ++		 *   clients unable to directly utilize the HTTP Basic authentication
    ++		 *   scheme (or other password-based HTTP authentication schemes).
     +		 *
     +		 * TODO: should we omit client_id from the body in this case?
     +		 */
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	 */
     +	if (response_code != 200
     +		&& response_code != 400
    -+		/*&& response_code != 401 TODO */)
    ++		 /* && response_code != 401 TODO */ )
     +	{
     +		actx_error(actx, "unexpected response code %ld", response_code);
     +		return false;
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	 */
     +	actx->errctx = "failed to parse device authorization";
     +	if (!parse_device_authz(actx, &actx->authz))
    -+		return false; /* error message already set */
    ++		return false;			/* error message already set */
     +
     +	return true;
     +}
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	const char *device_code = actx->authz.device_code;
     +	PQExpBuffer work_buffer = &actx->work_data;
     +
    -+	Assert(conn->oauth_client_id); /* ensured by get_auth_token() */
    -+	Assert(token_uri); /* ensured by get_discovery_document() */
    -+	Assert(device_code); /* ensured by run_device_authz() */
    ++	Assert(conn->oauth_client_id);	/* ensured by get_auth_token() */
    ++	Assert(token_uri);			/* ensured by get_discovery_document() */
    ++	Assert(device_code);		/* ensured by run_device_authz() */
     +
     +	/* Construct our request body. TODO: url-encode */
     +	resetPQExpBuffer(work_buffer);
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +
     +	if (conn->oauth_client_secret)
     +	{
    -+		/*
    ++		/*----
     +		 * Use HTTP Basic auth to send the password. Per RFC 6749, Sec. 2.3.1,
     +		 *
    -+		 *   Including the client credentials in the request-body using the two
    -+		 *   parameters is NOT RECOMMENDED and SHOULD be limited to clients
    -+		 *   unable to directly utilize the HTTP Basic authentication scheme (or
    -+		 *   other password-based HTTP authentication schemes).
    ++		 *   Including the client credentials in the request-body using the
    ++		 *   two parameters is NOT RECOMMENDED and SHOULD be limited to
    ++		 *   clients unable to directly utilize the HTTP Basic authentication
    ++		 *   scheme (or other password-based HTTP authentication schemes).
     +		 *
     +		 * TODO: should we omit client_id from the body in this case?
     +		 */
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	 */
     +	if (response_code != 200
     +		&& response_code != 400
    -+		/*&& response_code != 401 TODO */)
    ++		 /* && response_code != 401 TODO */ )
     +	{
     +		actx_error(actx, "unexpected response code %ld", response_code);
     +		return false;
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	{
     +		actx->errctx = "failed to parse access token response";
     +		if (!parse_access_token(actx, tok))
    -+			return false; /* error message already set */
    ++			return false;		/* error message already set */
     +	}
     +	else if (!parse_token_error(actx, &tok->err))
     +		return false;
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	 * probably need to consider both the TLS backend libcurl is compiled
     +	 * against and what the user has asked us to do via PQinit[Open]SSL.
     +	 *
    -+	 * Recent versions of libcurl have improved the thread-safety situation, but
    -+	 * you apparently can't check at compile time whether the implementation is
    -+	 * thread-safe, and there's a chicken-and-egg problem where you can't check
    -+	 * the thread safety until you've initialized cURL, which you can't do
    -+	 * before you've made sure it's thread-safe...
    ++	 * Recent versions of libcurl have improved the thread-safety situation,
    ++	 * but you apparently can't check at compile time whether the
    ++	 * implementation is thread-safe, and there's a chicken-and-egg problem
    ++	 * where you can't check the thread safety until you've initialized cURL,
    ++	 * which you can't do before you've made sure it's thread-safe...
     +	 *
    -+	 * We know we've already initialized Winsock by this point, so we should be
    -+	 * able to safely skip that bit. But we have to tell cURL to initialize
    ++	 * We know we've already initialized Winsock by this point, so we should
    ++	 * be able to safely skip that bit. But we have to tell cURL to initialize
     +	 * everything else, because other pieces of our client executable may
     +	 * already be using cURL for their own purposes. If we initialize libcurl
     +	 * first, with only a subset of its features, we could break those other
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	if (!state->async_ctx)
     +	{
     +		/*
    -+		 * Create our asynchronous state, and hook it into the upper-level OAuth
    -+		 * state immediately, so any failures below won't leak the context
    -+		 * allocation.
    ++		 * Create our asynchronous state, and hook it into the upper-level
    ++		 * OAuth state immediately, so any failures below won't leak the
    ++		 * context allocation.
     +		 */
     +		actx = calloc(1, sizeof(*actx));
     +		if (!actx)
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +		case OAUTH_STEP_DISCOVERY:
     +		case OAUTH_STEP_DEVICE_AUTHORIZATION:
     +		case OAUTH_STEP_TOKEN_REQUEST:
    -+		{
    -+			PostgresPollingStatusType status;
    ++			{
    ++				PostgresPollingStatusType status;
     +
    -+			status = drive_request(actx);
    ++				status = drive_request(actx);
     +
    -+			if (status == PGRES_POLLING_FAILED)
    -+				goto error_return;
    -+			else if (status != PGRES_POLLING_OK)
    -+			{
    -+				/* not done yet */
    -+				free_token(&tok);
    -+				return status;
    ++				if (status == PGRES_POLLING_FAILED)
    ++					goto error_return;
    ++				else if (status != PGRES_POLLING_OK)
    ++				{
    ++					/* not done yet */
    ++					free_token(&tok);
    ++					return status;
    ++				}
     +			}
    -+		}
     +
     +		case OAUTH_STEP_WAIT_INTERVAL:
     +			/* TODO check that the timer has expired */
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +			break;
     +
     +		case OAUTH_STEP_TOKEN_REQUEST:
    -+		{
    -+			const struct token_error *err;
    ++			{
    ++				const struct token_error *err;
     +#ifdef HAVE_SYS_EPOLL_H
    -+			struct itimerspec spec = {0};
    ++				struct itimerspec spec = {0};
     +#endif
     +#ifdef HAVE_SYS_EVENT_H
    -+			struct kevent ev = {0};
    ++				struct kevent ev = {0};
     +#endif
     +
    -+			if (!finish_token_request(actx, &tok))
    -+				goto error_return;
    ++				if (!finish_token_request(actx, &tok))
    ++					goto error_return;
     +
    -+			if (!actx->user_prompted)
    -+			{
    -+				int			res;
    -+				PQpromptOAuthDevice prompt = {
    -+					.verification_uri = actx->authz.verification_uri,
    -+					.user_code = actx->authz.user_code,
    -+					/* TODO: optional fields */
    -+				};
    ++				if (!actx->user_prompted)
    ++				{
    ++					int			res;
    ++					PQpromptOAuthDevice prompt = {
    ++						.verification_uri = actx->authz.verification_uri,
    ++						.user_code = actx->authz.user_code,
    ++						/* TODO: optional fields */
    ++					};
     +
    -+				/*
    -+				 * Now that we know the token endpoint isn't broken, give the
    -+				 * user the login instructions.
    -+				 */
    -+				res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn,
    -+									 &prompt);
    ++					/*
    ++					 * Now that we know the token endpoint isn't broken, give
    ++					 * the user the login instructions.
    ++					 */
    ++					res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn,
    ++										 &prompt);
     +
    -+				if (!res)
    -+				{
    -+					fprintf(stderr, "Visit %s and enter the code: %s",
    -+							prompt.verification_uri, prompt.user_code);
    -+				}
    -+				else if (res < 0)
    -+				{
    -+					actx_error(actx, "device prompt failed");
    -+					goto error_return;
    ++					if (!res)
    ++					{
    ++						fprintf(stderr, "Visit %s and enter the code: %s",
    ++								prompt.verification_uri, prompt.user_code);
    ++					}
    ++					else if (res < 0)
    ++					{
    ++						actx_error(actx, "device prompt failed");
    ++						goto error_return;
    ++					}
    ++
    ++					actx->user_prompted = true;
     +				}
     +
    -+				actx->user_prompted = true;
    -+			}
    ++				if (tok.access_token)
    ++				{
    ++					/* Construct our Bearer token. */
    ++					resetPQExpBuffer(&actx->work_data);
    ++					appendPQExpBuffer(&actx->work_data, "Bearer %s",
    ++									  tok.access_token);
     +
    -+			if (tok.access_token)
    -+			{
    -+				/* Construct our Bearer token. */
    -+				resetPQExpBuffer(&actx->work_data);
    -+				appendPQExpBuffer(&actx->work_data, "Bearer %s",
    -+								  tok.access_token);
    ++					if (PQExpBufferDataBroken(actx->work_data))
    ++					{
    ++						actx_error(actx, "out of memory");
    ++						goto error_return;
    ++					}
     +
    -+				if (PQExpBufferDataBroken(actx->work_data))
    -+				{
    -+					actx_error(actx, "out of memory");
    -+					goto error_return;
    ++					state->token = strdup(actx->work_data.data);
    ++					break;
     +				}
     +
    -+				state->token = strdup(actx->work_data.data);
    -+				break;
    -+			}
    -+
    -+			/*
    -+			 * authorization_pending and slow_down are the only acceptable
    -+			 * errors; anything else and we bail.
    -+			 */
    -+			err = &tok.err;
    -+			if (!err->error || (strcmp(err->error, "authorization_pending")
    -+								&& strcmp(err->error, "slow_down")))
    -+			{
    -+				/* TODO handle !err->error */
    -+				if (err->error_description)
    -+					appendPQExpBuffer(&actx->errbuf, "%s ",
    -+									  err->error_description);
    ++				/*
    ++				 * authorization_pending and slow_down are the only acceptable
    ++				 * errors; anything else and we bail.
    ++				 */
    ++				err = &tok.err;
    ++				if (!err->error || (strcmp(err->error, "authorization_pending")
    ++									&& strcmp(err->error, "slow_down")))
    ++				{
    ++					/* TODO handle !err->error */
    ++					if (err->error_description)
    ++						appendPQExpBuffer(&actx->errbuf, "%s ",
    ++										  err->error_description);
     +
    -+				appendPQExpBuffer(&actx->errbuf, "(%s)", err->error);
    ++					appendPQExpBuffer(&actx->errbuf, "(%s)", err->error);
     +
    -+				goto error_return;
    -+			}
    ++					goto error_return;
    ++				}
     +
    -+			/*
    -+			 * A slow_down error requires us to permanently increase our retry
    -+			 * interval by five seconds. RFC 8628, Sec. 3.5.
    -+			 */
    -+			if (!strcmp(err->error, "slow_down"))
    -+			{
    -+				actx->authz.interval += 5; /* TODO check for overflow? */
    -+			}
    ++				/*
    ++				 * A slow_down error requires us to permanently increase our
    ++				 * retry interval by five seconds. RFC 8628, Sec. 3.5.
    ++				 */
    ++				if (!strcmp(err->error, "slow_down"))
    ++				{
    ++					actx->authz.interval += 5;	/* TODO check for overflow? */
    ++				}
     +
    -+			/*
    -+			 * Wait for the required interval before issuing the next request.
    -+			 */
    -+			Assert(actx->authz.interval > 0);
    ++				/*
    ++				 * Wait for the required interval before issuing the next
    ++				 * request.
    ++				 */
    ++				Assert(actx->authz.interval > 0);
     +#ifdef HAVE_SYS_EPOLL_H
    -+			spec.it_value.tv_sec = actx->authz.interval;
    ++				spec.it_value.tv_sec = actx->authz.interval;
     +
    -+			if (timerfd_settime(actx->timerfd, 0 /* no flags */, &spec, NULL) < 0)
    -+			{
    -+				actx_error(actx, "failed to set timerfd: %m");
    -+				goto error_return;
    -+			}
    ++				if (timerfd_settime(actx->timerfd, 0 /* no flags */ , &spec, NULL) < 0)
    ++				{
    ++					actx_error(actx, "failed to set timerfd: %m");
    ++					goto error_return;
    ++				}
     +
    -+			*altsock = actx->timerfd;
    ++				*altsock = actx->timerfd;
     +#endif
     +#ifdef HAVE_SYS_EVENT_H
    -+			// XXX: I guess this wants to be hidden in a routine
    -+			EV_SET(&ev, 1, EVFILT_TIMER, EV_ADD, 0,
    -+				   actx->authz.interval * 1000, 0);
    -+			if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0)
    -+			{
    -+				actx_error(actx, "failed to set kqueue timer: %m");
    -+				goto error_return;
    -+			}
    -+			// XXX: why did we change the altsock in the epoll version?
    ++				/* XXX: I guess this wants to be hidden in a routine */
    ++				EV_SET(&ev, 1, EVFILT_TIMER, EV_ADD, 0,
    ++					   actx->authz.interval * 1000, 0);
    ++				if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0)
    ++				{
    ++					actx_error(actx, "failed to set kqueue timer: %m");
    ++					goto error_return;
    ++				}
    ++				/* XXX: why did we change the altsock in the epoll version? */
     +#endif
    -+			actx->step = OAUTH_STEP_WAIT_INTERVAL;
    -+			break;
    -+		}
    ++				actx->step = OAUTH_STEP_WAIT_INTERVAL;
    ++				break;
    ++			}
     +
     +		case OAUTH_STEP_WAIT_INTERVAL:
     +			actx->errctx = "failed to obtain access token";
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	return state->token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
     +
     +error_return:
    ++
     +	/*
     +	 * Assemble the three parts of our error: context, body, and detail. See
     +	 * also the documentation for struct async_ctx.
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +
     +	if (actx->curl_err[0])
     +	{
    -+		size_t len;
    ++		size_t		len;
     +
     +		appendPQExpBuffer(&conn->errorMessage, " (%s)", actx->curl_err);
     +
    @@ src/interfaces/libpq/fe-auth-oauth-iddawc.c (new)
     +run_iddawc_auth_flow(PGconn *conn, const char *discovery_uri)
     +{
     +	struct _i_session session;
    -+	PQExpBuffer	token_buf = NULL;
    ++	PQExpBuffer token_buf = NULL;
     +	int			err;
     +	int			auth_method;
     +	bool		user_prompted = false;
    @@ src/interfaces/libpq/fe-auth-oauth-iddawc.c (new)
     +		auth_method = I_TOKEN_AUTH_METHOD_SECRET_BASIC;
     +
     +	err = i_set_parameter_list(&session,
    -+		I_OPT_CLIENT_ID, conn->oauth_client_id,
    -+		I_OPT_CLIENT_SECRET, conn->oauth_client_secret,
    -+		I_OPT_TOKEN_METHOD, auth_method,
    -+		I_OPT_SCOPE, conn->oauth_scope,
    -+		I_OPT_NONE
    -+	);
    ++							   I_OPT_CLIENT_ID, conn->oauth_client_id,
    ++							   I_OPT_CLIENT_SECRET, conn->oauth_client_secret,
    ++							   I_OPT_TOKEN_METHOD, auth_method,
    ++							   I_OPT_SCOPE, conn->oauth_scope,
    ++							   I_OPT_NONE
    ++		);
     +	if (err)
     +	{
     +		iddawc_error(conn, err, "failed to set client identifier");
    @@ src/interfaces/libpq/fe-auth-oauth-iddawc.c (new)
     +	if (err)
     +	{
     +		iddawc_request_error(conn, &session, err,
    -+							"failed to obtain device authorization");
    ++							 "failed to obtain device authorization");
     +		goto cleanup;
     +	}
     +
    @@ src/interfaces/libpq/fe-auth-oauth-iddawc.c (new)
     +	}
     +
     +	/*
    -+	 * Poll the token endpoint until either the user logs in and authorizes the
    -+	 * use of a token, or a hard failure occurs. We perform one ping _before_
    -+	 * prompting the user, so that we don't make them do the work of logging in
    -+	 * only to find that the token endpoint is completely unreachable.
    ++	 * Poll the token endpoint until either the user logs in and authorizes
    ++	 * the use of a token, or a hard failure occurs. We perform one ping
    ++	 * _before_ prompting the user, so that we don't make them do the work of
    ++	 * logging in only to find that the token endpoint is completely
    ++	 * unreachable.
     +	 */
     +	err = i_run_token_request(&session);
     +	while (err)
    @@ src/interfaces/libpq/fe-auth-oauth-iddawc.c (new)
     +							&& strcmp(error_code, "slow_down")))
     +		{
     +			iddawc_request_error(conn, &session, err,
    -+								"failed to obtain access token");
    ++								 "failed to obtain access token");
     +			goto cleanup;
     +		}
     +
    @@ src/interfaces/libpq/fe-auth-oauth-iddawc.c (new)
     +			user_prompted = true;
     +		}
     +
    -+		/*
    -+		 * We are required to wait between polls; the server tells us how long.
    ++		/*---
    ++		 * We are required to wait between polls; the server tells us how
    ++		 * long.
     +		 * TODO: if interval's not set, we need to default to five seconds
     +		 * TODO: sanity check the interval
     +		 */
    @@ src/interfaces/libpq/fe-auth-oauth-iddawc.c (new)
     +
     +		/*
     +		 * XXX Reset the error code before every call, because iddawc won't do
    -+		 * that for us. This matters if the server first sends a "pending" error
    -+		 * code, then later hard-fails without sending an error code to
    ++		 * that for us. This matters if the server first sends a "pending"
    ++		 * error code, then later hard-fails without sending an error code to
     +		 * overwrite the first one.
     +		 *
     +		 * That we have to do this at all seems like a bug in iddawc.
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +static char *
     +client_initial_response(PGconn *conn, const char *token)
     +{
    -+	static const char * const resp_format = "n,," kvsep "auth=%s" kvsep kvsep;
    ++	static const char *const resp_format = "n,," kvsep "auth=%s" kvsep kvsep;
     +
     +	PQExpBufferData buf;
     +	char	   *response = NULL;
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +		 * Either programmer error, or something went badly wrong during the
     +		 * asynchronous fetch.
     +		 *
    -+		 * TODO: users shouldn't see this; what action should they take if they
    -+		 * do?
    ++		 * TODO: users shouldn't see this; what action should they take if
    ++		 * they do?
     +		 */
     +		libpq_append_conn_error(conn, "no OAuth token was set for the connection");
     +		return NULL;
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +
     +struct json_ctx
     +{
    -+	char		   *errmsg; /* any non-NULL value stops all processing */
    -+	PQExpBufferData errbuf; /* backing memory for errmsg */
    -+	int				nested; /* nesting level (zero is the top) */
    ++	char	   *errmsg;			/* any non-NULL value stops all processing */
    ++	PQExpBufferData errbuf;		/* backing memory for errmsg */
    ++	int			nested;			/* nesting level (zero is the top) */
     +
    -+	const char	   *target_field_name; /* points to a static allocation */
    -+	char		  **target_field;      /* see below */
    ++	const char *target_field_name;	/* points to a static allocation */
    ++	char	  **target_field;	/* see below */
     +
     +	/* target_field, if set, points to one of the following: */
    -+	char		   *status;
    -+	char		   *scope;
    -+	char		   *discovery_uri;
    ++	char	   *status;
    ++	char	   *scope;
    ++	char	   *discovery_uri;
     +};
     +
     +#define oauth_json_has_error(ctx) \
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +static JsonParseErrorType
     +oauth_json_object_start(void *state)
     +{
    -+	struct json_ctx	   *ctx = state;
    ++	struct json_ctx *ctx = state;
     +
     +	if (ctx->target_field)
     +	{
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +static JsonParseErrorType
     +oauth_json_object_end(void *state)
     +{
    -+	struct json_ctx	   *ctx = state;
    ++	struct json_ctx *ctx = state;
     +
     +	--ctx->nested;
     +	return JSON_SUCCESS;
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +static JsonParseErrorType
     +oauth_json_object_field_start(void *state, char *name, bool isnull)
     +{
    -+	struct json_ctx	   *ctx = state;
    ++	struct json_ctx *ctx = state;
     +
     +	if (ctx->nested == 1)
     +	{
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +static JsonParseErrorType
     +oauth_json_array_start(void *state)
     +{
    -+	struct json_ctx	   *ctx = state;
    ++	struct json_ctx *ctx = state;
     +
     +	if (!ctx->nested)
     +	{
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +static JsonParseErrorType
     +oauth_json_scalar(void *state, char *token, JsonTokenType type)
     +{
    -+	struct json_ctx	   *ctx = state;
    ++	struct json_ctx *ctx = state;
     +
     +	if (!ctx->nested)
     +	{
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +			ctx->target_field = NULL;
     +			ctx->target_field_name = NULL;
     +
    -+			return JSON_SUCCESS; /* don't free the token we're using */
    ++			return JSON_SUCCESS;	/* don't free the token we're using */
     +		}
     +
     +		oauth_json_set_error(ctx,
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +static bool
     +handle_oauth_sasl_error(PGconn *conn, char *msg, int msglen)
     +{
    -+	JsonLexContext		lex = {0};
    -+	JsonSemAction		sem = {0};
    -+	JsonParseErrorType	err;
    -+	struct json_ctx		ctx = {0};
    -+	char			   *errmsg = NULL;
    ++	JsonLexContext lex = {0};
    ++	JsonSemAction sem = {0};
    ++	JsonParseErrorType err;
    ++	struct json_ctx ctx = {0};
    ++	char	   *errmsg = NULL;
     +
     +	/* Sanity check. */
     +	if (strlen(msg) != msglen)
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +	else if (status == PGRES_POLLING_OK)
     +	{
     +		/*
    -+		 * We already have a token, so copy it into the state. (We can't
    -+		 * hold onto the original string, since it may not be safe for us to
    -+		 * free() it.)
    ++		 * We already have a token, so copy it into the state. (We can't hold
    ++		 * onto the original string, since it may not be safe for us to free()
    ++		 * it.)
     +		 */
    -+		PQExpBufferData	token;
    ++		PQExpBufferData token;
     +
     +		if (!request->token)
     +		{
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +		{
     +			/*
     +			 * We already have a token, so copy it into the state. (We can't
    -+			 * hold onto the original string, since it may not be safe for us to
    -+			 * free() it.)
    ++			 * hold onto the original string, since it may not be safe for us
    ++			 * to free() it.)
     +			 */
    -+			PQExpBufferData	token;
    ++			PQExpBufferData token;
     +
     +			initPQExpBuffer(&token);
     +			appendPQExpBuffer(&token, "Bearer %s", request.token);
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +	{
     +		/*
     +		 * Either we already have one, or we aren't able to derive one
    -+		 * ourselves. The latter case is not an error condition; we'll just ask
    -+		 * the server to provide one for us.
    ++		 * ourselves. The latter case is not an error condition; we'll just
    ++		 * ask the server to provide one for us.
     +		 */
     +		return true;
     +	}
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +				}
     +
     +				/*
    -+				 * Decide whether we're using a user-provided OAuth flow, or the
    -+				 * one we have built in.
    ++				 * Decide whether we're using a user-provided OAuth flow, or
    ++				 * the one we have built in.
     +				 */
     +				if (!setup_token_request(conn, state))
     +					return SASL_FAILED;
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +				if (state->token)
     +				{
     +					/*
    -+					 * A really smart user implementation may have already given
    -+					 * us the token (e.g. if there was an unexpired copy already
    -+					 * cached). In that case, we can just fall through.
    ++					 * A really smart user implementation may have already
    ++					 * given us the token (e.g. if there was an unexpired copy
    ++					 * already cached). In that case, we can just fall
    ++					 * through.
     +					 */
     +				}
     +				else
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +					/*
     +					 * Otherwise, we have to hand the connection over to our
     +					 * OAuth implementation. This involves a number of HTTP
    -+					 * connections and timed waits, so we escape the synchronous
    -+					 * auth processing and tell PQconnectPoll to transfer
    -+					 * control to our async implementation.
    ++					 * connections and timed waits, so we escape the
    ++					 * synchronous auth processing and tell PQconnectPoll to
    ++					 * transfer control to our async implementation.
     +					 */
    -+					Assert(conn->async_auth); /* should have been set already */
    ++					Assert(conn->async_auth);	/* should have been set
    ++												 * already */
     +					state->state = FE_OAUTH_REQUESTING_TOKEN;
     +					return SASL_ASYNC;
     +				}
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +			 * Respond with the required dummy message (RFC 7628, sec. 3.2.3).
     +			 */
     +			*output = strdup(kvsep);
    -+			*outputlen = strlen(*output); /* == 1 */
    ++			*outputlen = strlen(*output);	/* == 1 */
     +
     +			state->state = FE_OAUTH_SERVER_ERROR;
     +			return SASL_CONTINUE;
     +
     +		case FE_OAUTH_SERVER_ERROR:
    ++
     +			/*
    -+			 * After an error, the server should send an error response to fail
    -+			 * the SASL handshake, which is handled in higher layers.
    ++			 * After an error, the server should send an error response to
    ++			 * fail the SASL handshake, which is handled in higher layers.
     +			 *
    -+			 * If we get here, the server either sent *another* challenge which
    -+			 * isn't defined in the RFC, or completed the handshake successfully
    -+			 * after telling us it was going to fail. Neither is acceptable.
    ++			 * If we get here, the server either sent *another* challenge
    ++			 * which isn't defined in the RFC, or completed the handshake
    ++			 * successfully after telling us it was going to fail. Neither is
    ++			 * acceptable.
     +			 */
     +			appendPQExpBufferStr(&conn->errorMessage,
     +								 libpq_gettext("server sent additional OAuth data after error\n"));
    @@ src/interfaces/libpq/fe-auth-oauth.h (new)
     +	char	   *token;
     +
     +	void	   *async_ctx;
    -+	void	  (*free_async_ctx) (PGconn *conn, void *ctx);
    ++	void		(*free_async_ctx) (PGconn *conn, void *ctx);
     +} fe_oauth_state;
     +
     +extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn, pgsocket *altsock);
    @@ src/interfaces/libpq/fe-auth-scram.c: scram_exchange(void *opaq, char *input, in
     +			return SASL_CONTINUE;
      
      		case FE_SCRAM_PROOF_SENT:
    -+		{
    -+			bool		match;
    -+
    - 			/* Receive server signature */
    - 			if (!read_server_final_message(state, input))
    +-			/* Receive server signature */
    +-			if (!read_server_final_message(state, input))
     -				goto error;
    -+				return SASL_FAILED;
    - 
    - 			/*
    - 			 * Verify server signature, to make sure we're talking to the
    - 			 * genuine server.
    - 			 */
    +-
    +-			/*
    +-			 * Verify server signature, to make sure we're talking to the
    +-			 * genuine server.
    +-			 */
     -			if (!verify_server_signature(state, success, &errstr))
    -+			if (!verify_server_signature(state, &match, &errstr))
    - 			{
    - 				libpq_append_conn_error(conn, "could not verify server signature: %s", errstr);
    --				goto error;
    -+				return SASL_FAILED;
    - 			}
    - 
    --			if (!*success)
     -			{
    -+			if (!match)
    - 				libpq_append_conn_error(conn, "incorrect server signature");
    +-				libpq_append_conn_error(conn, "could not verify server signature: %s", errstr);
    +-				goto error;
     -			}
    --			*done = true;
    +-
    +-			if (!*success)
    + 			{
    +-				libpq_append_conn_error(conn, "incorrect server signature");
    ++				bool		match;
    ++
    ++				/* Receive server signature */
    ++				if (!read_server_final_message(state, input))
    ++					return SASL_FAILED;
    ++
    ++				/*
    ++				 * Verify server signature, to make sure we're talking to the
    ++				 * genuine server.
    ++				 */
    ++				if (!verify_server_signature(state, &match, &errstr))
    ++				{
    ++					libpq_append_conn_error(conn, "could not verify server signature: %s", errstr);
    ++					return SASL_FAILED;
    ++				}
    ++
    ++				if (!match)
    ++					libpq_append_conn_error(conn, "incorrect server signature");
     +
    - 			state->state = FE_SCRAM_FINISHED;
    - 			state->conn->client_finished_auth = true;
    ++				state->state = FE_SCRAM_FINISHED;
    ++				state->conn->client_finished_auth = true;
    ++				return match ? SASL_COMPLETE : SASL_FAILED;
    + 			}
    +-			*done = true;
    +-			state->state = FE_SCRAM_FINISHED;
    +-			state->conn->client_finished_auth = true;
     -			break;
    -+			return match ? SASL_COMPLETE : SASL_FAILED;
    -+		}
      
      		default:
      			/* shouldn't happen */
    @@ src/interfaces/libpq/fe-auth.c: pg_SASL_init(PGconn *conn, int payloadlen)
      		}
     +#ifdef USE_OAUTH
     +		else if (strcmp(mechanism_buf.data, OAUTHBEARER_NAME) == 0 &&
    -+				!selected_mechanism)
    ++				 !selected_mechanism)
     +		{
     +			selected_mechanism = OAUTHBEARER_NAME;
     +			conn->sasl = &pg_oauth_mech;
    @@ src/interfaces/libpq/fe-auth.c: pg_SASL_init(PGconn *conn, int payloadlen)
     +	if (status == SASL_ASYNC)
     +	{
     +		/*
    -+		 * The mechanism should have set up the necessary callbacks; all we need
    -+		 * to do is signal the caller.
    ++		 * The mechanism should have set up the necessary callbacks; all we
    ++		 * need to do is signal the caller.
     +		 */
     +		*async = true;
     +		return STATUS_OK;
    @@ src/interfaces/libpq/fe-auth.c: pg_SASL_continue(PGconn *conn, int payloadlen, b
     +	if (status == SASL_ASYNC)
     +	{
     +		/*
    -+		 * The mechanism should have set up the necessary callbacks; all we need
    -+		 * to do is signal the caller.
    ++		 * The mechanism should have set up the necessary callbacks; all we
    ++		 * need to do is signal the caller.
     +		 */
     +		*async = true;
     +		return STATUS_OK;
    @@ src/interfaces/libpq/fe-auth.c: PQchangePassword(PGconn *conn, const char *user,
     +int
     +PQdefaultAuthDataHook(PGAuthData type, PGconn *conn, void *data)
     +{
    -+	return 0; /* handle nothing */
    ++	return 0;					/* handle nothing */
     +}
     
      ## src/interfaces/libpq/fe-auth.h ##
    @@ src/interfaces/libpq/fe-connect.c: keep_going:						/* We will come back to here
     +				if (async && (res == STATUS_OK))
     +				{
     +					/*
    -+					 * We'll come back later once we're ready to respond. Don't
    -+					 * consume the request yet.
    ++					 * We'll come back later once we're ready to respond.
    ++					 * Don't consume the request yet.
     +					 */
     +					conn->status = CONNECTION_AUTHENTICATING;
     +					goto keep_going;
    @@ src/interfaces/libpq/fe-connect.c: keep_going:						/* We will come back to here
     +					 * received it.
     +					 */
     +					conn->status = CONNECTION_AWAITING_RESPONSE;
    -+					conn->altsock = PGINVALID_SOCKET; /* TODO: what frees this? */
    ++					conn->altsock = PGINVALID_SOCKET;	/* TODO: what frees
    ++														 * this? */
     +					goto keep_going;
     +				}
     +
    @@ src/interfaces/libpq/libpq-fe.h: typedef enum
      
     +typedef enum
     +{
    -+	PQAUTHDATA_PROMPT_OAUTH_DEVICE,	/* user must visit a device-authorization URL */
    ++	PQAUTHDATA_PROMPT_OAUTH_DEVICE, /* user must visit a device-authorization
    ++									 * URL */
     +	PQAUTHDATA_OAUTH_BEARER_TOKEN,	/* server requests an OAuth Bearer token */
     +} PGAuthData;
     +
    @@ src/interfaces/libpq/libpq-fe.h: extern int	PQenv2encoding(void);
      
     +typedef struct _PQpromptOAuthDevice
     +{
    -+	const char *verification_uri;			/* verification URI to visit */
    -+	const char *user_code;					/* user code to enter */
    ++	const char *verification_uri;	/* verification URI to visit */
    ++	const char *user_code;		/* user code to enter */
     +} PQpromptOAuthDevice;
     +
     +typedef struct _PQoauthBearerRequest
     +{
     +	/* Hook inputs (constant across all calls) */
    -+	const char * const openid_configuration;	/* OIDC discovery URI */
    -+	const char * const scope;					/* required scope(s), or NULL */
    ++	const char *const openid_configuration; /* OIDC discovery URI */
    ++	const char *const scope;	/* required scope(s), or NULL */
     +
     +	/* Hook outputs */
     +
    -+	/*
    ++	/*---------
     +	 * Callback implementing a custom asynchronous OAuth flow.
     +	 *
     +	 * The callback may return
    -+	 * - PGRES_POLLING_READING/WRITING, to indicate that a file descriptor has
    -+	 *   been stored in *altsock and libpq should wait until it is readable or
    -+	 *   writable before calling back;
    ++	 * - PGRES_POLLING_READING/WRITING, to indicate that a file descriptor
    ++	 *   has been stored in *altsock and libpq should wait until it is
    ++	 *   readable or writable before calling back;
     +	 * - PGRES_POLLING_OK, to indicate that the flow is complete and
     +	 *   request->token has been set; or
     +	 * - PGRES_POLLING_FAILED, to indicate that token retrieval has failed.
     +	 *
    -+	 * This callback is optional. If the token can be obtained without blocking
    -+	 * during the original call to the PQAUTHDATA_OAUTH_BEARER_TOKEN hook, it
    -+	 * may be returned directly, but one of request->async or request->token
    -+	 * must be set by the hook.
    ++	 * This callback is optional. If the token can be obtained without
    ++	 * blocking during the original call to the PQAUTHDATA_OAUTH_BEARER_TOKEN
    ++	 * hook, it may be returned directly, but one of request->async or
    ++	 * request->token must be set by the hook.
     +	 */
     +	PostgresPollingStatusType (*async) (PGconn *conn,
     +										struct _PQoauthBearerRequest *request,
    @@ src/interfaces/libpq/libpq-fe.h: extern int	PQenv2encoding(void);
     +	 * Callback to clean up custom allocations. A hook implementation may use
     +	 * this to free request->token and any resources in request->user.
     +	 *
    -+	 * This is technically optional, but highly recommended, because there is no
    -+	 * other indication as to when it is safe to free the token.
    ++	 * This is technically optional, but highly recommended, because there is
    ++	 * no other indication as to when it is safe to free the token.
     +	 */
    -+	void	  (*cleanup) (PGconn *conn, struct _PQoauthBearerRequest *request);
    ++	void		(*cleanup) (PGconn *conn, struct _PQoauthBearerRequest *request);
     +
     +	/*
    -+	 * The hook should set this to the Bearer token contents for the connection,
    -+	 * once the flow is completed.  The token contents must remain available to
    -+	 * libpq until the hook's cleanup callback is called.
    ++	 * The hook should set this to the Bearer token contents for the
    ++	 * connection, once the flow is completed.  The token contents must remain
    ++	 * available to libpq until the hook's cleanup callback is called.
     +	 */
     +	char	   *token;
     +
     +	/*
    -+	 * Hook-defined data. libpq will not modify this pointer across calls to the
    -+	 * async callback, so it can be used to keep track of application-specific
    -+	 * state. Resources allocated here should be freed by the cleanup callback.
    ++	 * Hook-defined data. libpq will not modify this pointer across calls to
    ++	 * the async callback, so it can be used to keep track of
    ++	 * application-specific state. Resources allocated here should be freed by
    ++	 * the cleanup callback.
     +	 */
     +	void	   *user;
     +} PQoauthBearerRequest;
    @@ src/interfaces/libpq/libpq-fe.h: extern int	PQenv2encoding(void);
      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);
    ++extern void PQsetAuthDataHook(PQauthDataHook_type hook);
     +extern PQauthDataHook_type PQgetAuthDataHook(void);
     +extern int	PQdefaultAuthDataHook(PGAuthData type, PGconn *conn, void *data);
     +
    @@ src/interfaces/libpq/libpq-int.h: struct pg_conn
      	char	   *load_balance_hosts; /* load balance over hosts */
      
     +	/* OAuth v2 */
    -+	char	   *oauth_issuer;			/* token issuer URL */
    -+	char	   *oauth_discovery_uri;	/* URI of the issuer's discovery document */
    -+	char	   *oauth_client_id;		/* client identifier */
    ++	char	   *oauth_issuer;	/* token issuer URL */
    ++	char	   *oauth_discovery_uri;	/* URI of the issuer's discovery
    ++										 * document */
    ++	char	   *oauth_client_id;	/* client identifier */
     +	char	   *oauth_client_secret;	/* client secret */
    -+	char	   *oauth_scope;			/* access token scope */
    -+	bool		oauth_want_retry;		/* should we retry on failure? */
    ++	char	   *oauth_scope;	/* access token scope */
    ++	bool		oauth_want_retry;	/* should we retry on failure? */
     +
      	/* Optional file to write trace info to */
      	FILE	   *Pfdebug;
    @@ src/makefiles/meson.build: pgxs_deps = {
        'pam': pam,
        'perl': perl_dep,
        'python': python3_dep,
    +
    + ## src/tools/pgindent/typedefs.list ##
    +@@ src/tools/pgindent/typedefs.list: ArrayMetaState
    + ArraySubWorkspace
    + ArrayToken
    + ArrayType
    ++AsyncAuthFunc
    + AsyncQueueControl
    + AsyncQueueEntry
    + AsyncRequest
    +@@ src/tools/pgindent/typedefs.list: CState
    + CTECycleClause
    + CTEMaterialize
    + CTESearchClause
    ++CURL
    ++CURLM
    + CV
    + CachedExpression
    + CachedPlan
    +@@ src/tools/pgindent/typedefs.list: NumericDigit
    + NumericSortSupport
    + NumericSumAccum
    + NumericVar
    ++OAuthStep
    + OM_uint32
    + OP
    + OSAPerGroupState
    +@@ src/tools/pgindent/typedefs.list: PFN
    + PGAlignedBlock
    + PGAlignedXLogBlock
    + PGAsyncStatusType
    ++PGAuthData
    + PGCALL2
    + PGChecksummablePage
    + PGContextVisibility
    +@@ src/tools/pgindent/typedefs.list: PQArgBlock
    + PQEnvironmentOption
    + PQExpBuffer
    + PQExpBufferData
    ++PQauthDataHook_type
    + PQcommMethods
    + PQconninfoOption
    + PQnoticeProcessor
    + PQnoticeReceiver
    ++PQoauthBearerRequest
    + PQprintOpt
    ++PQpromptOAuthDevice
    + PQsslKeyPassHook_OpenSSL_type
    + PREDICATELOCK
    + PREDICATELOCKTAG
    +@@ src/tools/pgindent/typedefs.list: RuleLock
    + RuleStmt
    + RunningTransactions
    + RunningTransactionsData
    ++SASLStatus
    + SC_HANDLE
    + SECURITY_ATTRIBUTES
    + SECURITY_STATUS
    +@@ src/tools/pgindent/typedefs.list: explain_get_index_name_hook_type
    + f_smgr
    + fasthash_state
    + fd_set
    ++fe_oauth_state
    ++fe_oauth_state_enum
    + fe_scram_state
    + fe_scram_state_enum
    + fetch_range_request
3:  13cf3f80b8 ! 3:  c56bc808b6 backend: add OAUTHBEARER SASL mechanism
    @@ src/backend/libpq/auth-oauth.c (new)
     +#include "storage/fd.h"
     +
     +/* GUC */
    -+char *oauth_validator_command;
    ++char	   *oauth_validator_command;
     +
    -+static void  oauth_get_mechanisms(Port *port, StringInfo buf);
    ++static void oauth_get_mechanisms(Port *port, StringInfo buf);
     +static void *oauth_init(Port *port, const char *selected_mech, const char *shadow_pass);
    -+static int   oauth_exchange(void *opaq, const char *input, int inputlen,
    -+							char **output, int *outputlen, const char **logdetail);
    ++static int	oauth_exchange(void *opaq, const char *input, int inputlen,
    ++						   char **output, int *outputlen, const char **logdetail);
     +
     +/* Mechanism declaration */
     +const pg_be_sasl_mech pg_be_oauth_mech = {
    @@ src/backend/libpq/auth-oauth.c (new)
     +
     +struct oauth_ctx
     +{
    -+	oauth_state	state;
    ++	oauth_state state;
     +	Port	   *port;
     +	const char *issuer;
     +	const char *scope;
    @@ src/backend/libpq/auth-oauth.c (new)
     +oauth_exchange(void *opaq, const char *input, int inputlen,
     +			   char **output, int *outputlen, const char **logdetail)
     +{
    -+	char   *p;
    -+	char	cbind_flag;
    -+	char   *auth;
    ++	char	   *p;
    ++	char		cbind_flag;
    ++	char	   *auth;
     +
     +	struct oauth_ctx *ctx = opaq;
     +
    @@ src/backend/libpq/auth-oauth.c (new)
     +			break;
     +
     +		case OAUTH_STATE_ERROR:
    ++
     +			/*
     +			 * Only one response is valid for the client during authentication
     +			 * failure: a single kvsep.
    @@ src/backend/libpq/auth-oauth.c (new)
     +
     +	/*
     +	 * OAUTHBEARER does not currently define a channel binding (so there is no
    -+	 * OAUTHBEARER-PLUS, and we do not accept a 'p' specifier). We accept a 'y'
    -+	 * specifier purely for the remote chance that a future specification could
    -+	 * define one; then future clients can still interoperate with this server
    -+	 * implementation. 'n' is the expected case.
    ++	 * OAUTHBEARER-PLUS, and we do not accept a 'p' specifier). We accept a
    ++	 * 'y' specifier purely for the remote chance that a future specification
    ++	 * could define one; then future clients can still interoperate with this
    ++	 * server implementation. 'n' is the expected case.
     +	 */
     +	cbind_flag = *p;
     +	switch (cbind_flag)
    @@ src/backend/libpq/auth-oauth.c (new)
     +					 errdetail("The server does not support channel binding for OAuth, but the client message includes channel binding data.")));
     +			break;
     +
    -+		case 'y': /* fall through */
    ++		case 'y':				/* fall through */
     +		case 'n':
     +			p++;
     +			if (*p != ',')
    @@ src/backend/libpq/auth-oauth.c (new)
     +static char *
     +parse_kvpairs_for_auth(char **input)
     +{
    -+	char   *pos = *input;
    -+	char   *auth = NULL;
    ++	char	   *pos = *input;
    ++	char	   *auth = NULL;
     +
    -+	/*
    ++	/*----
     +	 * The relevant ABNF, from Sec. 3.1:
     +	 *
     +	 *     kvsep          = %x01
    @@ src/backend/libpq/auth-oauth.c (new)
     +
     +	while (*pos)
     +	{
    -+		char   *end;
    -+		char   *sep;
    -+		char   *key;
    -+		char   *value;
    ++		char	   *end;
    ++		char	   *sep;
    ++		char	   *key;
    ++		char	   *value;
     +
     +		/*
     +		 * Find the end of this kvpair. Note that input is null-terminated by
    @@ src/backend/libpq/auth-oauth.c (new)
     +		/*
     +		 * Find the end of the key name.
     +		 *
    -+		 * TODO further validate the key/value grammar? empty keys, bad chars...
    ++		 * TODO further validate the key/value grammar? empty keys, bad
    ++		 * chars...
     +		 */
     +		sep = strchr(pos, '=');
     +		if (!sep)
    @@ src/backend/libpq/auth-oauth.c (new)
     +		{
     +			/*
     +			 * The RFC also defines the host and port keys, but they are not
    -+			 * required for OAUTHBEARER and we do not use them. Also, per
    -+			 * Sec. 3.1, any key/value pairs we don't recognize must be ignored.
    ++			 * required for OAUTHBEARER and we do not use them. Also, per Sec.
    ++			 * 3.1, any key/value pairs we don't recognize must be ignored.
     +			 */
     +		}
     +
    @@ src/backend/libpq/auth-oauth.c (new)
     +			 errmsg("malformed OAUTHBEARER message"),
     +			 errdetail("Message did not contain a final terminator.")));
     +
    -+	return NULL; /* unreachable */
    ++	return NULL;				/* unreachable */
     +}
     +
     +static void
     +generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen)
     +{
    -+	StringInfoData	buf;
    ++	StringInfoData buf;
     +
     +	/*
    -+	 * The admin needs to set an issuer and scope for OAuth to work. There's not
    -+	 * really a way to hide this from the user, either, because we can't choose
    -+	 * a "default" issuer, so be honest in the failure message.
    ++	 * The admin needs to set an issuer and scope for OAuth to work. There's
    ++	 * not really a way to hide this from the user, either, because we can't
    ++	 * choose a "default" issuer, so be honest in the failure message.
     +	 *
     +	 * TODO: see if there's a better place to fail, earlier than this.
     +	 */
    @@ src/backend/libpq/auth-oauth.c (new)
     +	 * TODO: JSON escaping
     +	 */
     +	appendStringInfo(&buf,
    -+		"{ "
    -+			"\"status\": \"invalid_token\", "
    -+			"\"openid-configuration\": \"%s/.well-known/openid-configuration\","
    -+			"\"scope\": \"%s\" "
    -+		"}",
    -+		ctx->issuer, ctx->scope);
    ++					 "{ "
    ++					 "\"status\": \"invalid_token\", "
    ++					 "\"openid-configuration\": \"%s/.well-known/openid-configuration\", "
    ++					 "\"scope\": \"%s\" "
    ++					 "}",
    ++					 ctx->issuer, ctx->scope);
     +
     +	*output = buf.data;
     +	*outputlen = buf.len;
    @@ src/backend/libpq/auth-oauth.c (new)
     +static bool
     +validate(Port *port, const char *auth, const char **logdetail)
     +{
    -+	static const char * const b64_set = "abcdefghijklmnopqrstuvwxyz"
    -+										"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    -+										"0123456789-._~+/";
    ++	static const char *const b64_set =
    ++		"abcdefghijklmnopqrstuvwxyz"
    ++		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    ++		"0123456789-._~+/";
     +
     +	const char *token;
     +	size_t		span;
    @@ src/backend/libpq/auth-oauth.c (new)
     +
     +	/* TODO: handle logdetail when the test framework can check it */
     +
    -+	/*
    ++	/*-----
     +	 * Only Bearer tokens are accepted. The ABNF is defined in RFC 6750, Sec.
     +	 * 2.1:
     +	 *
    @@ src/backend/libpq/auth-oauth.c (new)
     +	 *
     +	 * Since that spec is subordinate to HTTP (i.e. the HTTP Authorization
     +	 * header format; RFC 7235 Sec. 2), the "Bearer" scheme string must be
    -+	 * compared case-insensitively. (This is not mentioned in RFC 6750, but it's
    -+	 * pointed out in RFC 7628 Sec. 4.)
    ++	 * compared case-insensitively. (This is not mentioned in RFC 6750, but
    ++	 * it's pointed out in RFC 7628 Sec. 4.)
     +	 *
     +	 * TODO: handle the Authorization spec, RFC 7235 Sec. 2.1.
     +	 */
    @@ src/backend/libpq/auth-oauth.c (new)
     +	/*
     +	 * Before invoking the validator command, sanity-check the token format to
     +	 * avoid any injection attacks later in the chain. Invalid formats are
    -+	 * technically a protocol violation, but don't reflect any information about
    -+	 * the sensitive Bearer token back to the client; log at COMMERROR instead.
    ++	 * technically a protocol violation, but don't reflect any information
    ++	 * about the sensitive Bearer token back to the client; log at COMMERROR
    ++	 * instead.
     +	 */
     +
     +	/* Tokens must not be empty. */
    @@ src/backend/libpq/auth-oauth.c (new)
     +	}
     +
     +	/*
    -+	 * Make sure the token contains only allowed characters. Tokens may end with
    -+	 * any number of '=' characters.
    ++	 * Make sure the token contains only allowed characters. Tokens may end
    ++	 * with any number of '=' characters.
     +	 */
     +	span = strspn(token, b64_set);
     +	while (token[span] == '=')
    @@ src/backend/libpq/auth-oauth.c (new)
     +	if (token[span] != '\0')
     +	{
     +		/*
    -+		 * This error message could be more helpful by printing the problematic
    -+		 * character(s), but that'd be a bit like printing a piece of someone's
    -+		 * password into the logs.
    ++		 * This error message could be more helpful by printing the
    ++		 * problematic character(s), but that'd be a bit like printing a piece
    ++		 * of someone's password into the logs.
     +		 */
     +		ereport(COMMERROR,
     +				(errcode(ERRCODE_PROTOCOL_VIOLATION),
    @@ src/backend/libpq/auth-oauth.c (new)
     +		/*
     +		 * If the validator is our authorization authority, we're done.
     +		 * Authentication may or may not have been performed depending on the
    -+		 * validator implementation; all that matters is that the validator says
    -+		 * the user can log in with the target role.
    ++		 * validator implementation; all that matters is that the validator
    ++		 * says the user can log in with the target role.
     +		 */
     +		return true;
     +	}
    @@ src/backend/libpq/auth-oauth.c (new)
     +	int			rfd = -1;
     +	int			wfd = -1;
     +
    -+	StringInfoData command = { 0 };
    ++	StringInfoData command = {0};
     +	char	   *p;
     +	FILE	   *fh = NULL;
     +
    @@ src/backend/libpq/auth-oauth.c (new)
     +		return false;
     +	}
     +
    -+	/*
    -+	 * Since popen() is unidirectional, open up a pipe for the other direction.
    -+	 * Use CLOEXEC to ensure that our write end doesn't accidentally get copied
    -+	 * into child processes, which would prevent us from closing it cleanly.
    ++	/*------
    ++	 * Since popen() is unidirectional, open up a pipe for the other
    ++	 * direction. Use CLOEXEC to ensure that our write end doesn't
    ++	 * accidentally get copied into child processes, which would prevent us
    ++	 * from closing it cleanly.
     +	 *
     +	 * XXX this is ugly. We should just read from the child process's stdout,
     +	 * but that's a lot more code.
    @@ src/backend/libpq/auth-oauth.c (new)
     +		goto cleanup;
     +	}
     +
    -+	/*
    ++	/*----------
     +	 * Construct the command, substituting any recognized %-specifiers:
     +	 *
     +	 *   %f: the file descriptor of the input pipe
    @@ src/backend/libpq/auth-oauth.c (new)
     +					p++;
     +					break;
     +				case 'r':
    ++
     +					/*
    -+					 * TODO: decide how this string should be escaped. The role
    -+					 * is controlled by the client, so if we don't escape it,
    -+					 * command injections are inevitable.
    ++					 * TODO: decide how this string should be escaped. The
    ++					 * role is controlled by the client, so if we don't escape
    ++					 * it, command injections are inevitable.
     +					 *
     +					 * This is probably an indication that the role name needs
    -+					 * to be communicated to the validator process in some other
    -+					 * way. For this proof of concept, just be incredibly strict
    -+					 * about the characters that are allowed in user names.
    ++					 * to be communicated to the validator process in some
    ++					 * other way. For this proof of concept, just be
    ++					 * incredibly strict about the characters that are allowed
    ++					 * in user names.
     +					 */
     +					if (!username_ok_for_shell(port->user_name))
     +						goto cleanup;
    @@ src/backend/libpq/auth-oauth.c (new)
     +	close(wfd);
     +	wfd = -1;
     +
    -+	/*
    ++	/*-----
     +	 * Read the command's response.
     +	 *
     +	 * TODO: getline() is probably too new to use, unfortunately.
    @@ src/backend/libpq/auth-oauth.c (new)
     +static bool
     +check_exit(FILE **fh, const char *command)
     +{
    -+	int rc;
    ++	int			rc;
     +
     +	rc = ClosePipeStream(*fh);
     +	*fh = NULL;
    @@ src/backend/libpq/auth-oauth.c (new)
     +	}
     +	else if (rc != 0)
     +	{
    -+		char *reason = wait_result_to_str(rc);
    ++		char	   *reason = wait_result_to_str(rc);
     +
     +		ereport(COMMERROR,
     +				(errmsg("failed to execute command \"%s\": %s",
    @@ src/backend/libpq/auth-oauth.c (new)
     +username_ok_for_shell(const char *username)
     +{
     +	/* This set is borrowed from fe_utils' appendShellStringNoError(). */
    -+	static const char * const allowed = "abcdefghijklmnopqrstuvwxyz"
    -+										"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    -+										"0123456789-_./:";
    -+	size_t	span;
    ++	static const char *const allowed =
    ++		"abcdefghijklmnopqrstuvwxyz"
    ++		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    ++		"0123456789-_./:";
    ++	size_t		span;
     +
    -+	Assert(username && username[0]); /* should have already been checked */
    ++	Assert(username && username[0]);	/* should have already been checked */
     +
     +	span = strspn(username, allowed);
     +	if (username[span] != '\0')
    @@ src/include/libpq/oauth.h (new)
     +/* Implementation */
     +extern const pg_be_sasl_mech pg_be_oauth_mech;
     +
    -+#endif /* PG_OAUTH_H */
    ++#endif							/* PG_OAUTH_H */
     
      ## src/include/libpq/sasl.h ##
     @@
    @@ src/include/libpq/sasl.h: typedef struct pg_be_sasl_mech
      } pg_be_sasl_mech;
      
      /* Common implementation for auth.c */
    +
    + ## src/tools/pgindent/typedefs.list ##
    +@@ src/tools/pgindent/typedefs.list: normal_rand_fctx
    + nsphash_hash
    + ntile_context
    + numeric
    ++oauth_state
    + object_access_hook_type
    + object_access_hook_type_str
    + off_t
4:  83a55ba4eb = 4:  35ca8abdad Add pytest suite for OAuth
5:  49a3b2dfd1 = 5:  fb4cac4e99 squash! Add pytest suite for OAuth
6:  a68494323f = 6:  2008e60b3c XXX temporary patches to build and test
-:  ---------- > 7:  64611d33ef REVERT: temporarily skip the exit check