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(©lex, 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