since-v14.diff.txt
text/plain
Filename: since-v14.diff.txt
Type: text/plain
Part: 0
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