LCOV - code coverage report
Current view: top level - src/interfaces/libpq - fe-auth-oauth.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 0.8 % 398 3
Test Date: 2026-05-09 14:16:36 Functions: 4.3 % 23 1
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * fe-auth-oauth.c
       4              :  *     The front-end (client) implementation of OAuth/OIDC authentication
       5              :  *     using the SASL OAUTHBEARER mechanism.
       6              :  *
       7              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       8              :  * Portions Copyright (c) 1994, Regents of the University of California
       9              :  *
      10              :  * IDENTIFICATION
      11              :  *    src/interfaces/libpq/fe-auth-oauth.c
      12              :  *
      13              :  *-------------------------------------------------------------------------
      14              :  */
      15              : 
      16              : #include "postgres_fe.h"
      17              : 
      18              : #ifdef USE_DYNAMIC_OAUTH
      19              : #include <dlfcn.h>
      20              : #endif
      21              : 
      22              : #include "common/base64.h"
      23              : #include "common/hmac.h"
      24              : #include "common/jsonapi.h"
      25              : #include "common/oauth-common.h"
      26              : #include "fe-auth.h"
      27              : #include "fe-auth-oauth.h"
      28              : #include "mb/pg_wchar.h"
      29              : #include "oauth-debug.h"
      30              : #include "pg_config_paths.h"
      31              : #include "utils/memdebug.h"
      32              : 
      33              : static PostgresPollingStatusType do_async(fe_oauth_state *state,
      34              :                                           PGoauthBearerRequestV2 *request);
      35              : static void do_cleanup(fe_oauth_state *state, PGoauthBearerRequestV2 *request);
      36              : static void poison_req_v2(PGoauthBearerRequestV2 *request, bool poison);
      37              : 
      38              : /* The exported OAuth callback mechanism. */
      39              : static void *oauth_init(PGconn *conn, const char *password,
      40              :                         const char *sasl_mechanism);
      41              : static SASLStatus oauth_exchange(void *opaq, bool final,
      42              :                                  char *input, int inputlen,
      43              :                                  char **output, int *outputlen);
      44              : static bool oauth_channel_bound(void *opaq);
      45              : static void oauth_free(void *opaq);
      46              : 
      47              : const pg_fe_sasl_mech pg_oauth_mech = {
      48              :     oauth_init,
      49              :     oauth_exchange,
      50              :     oauth_channel_bound,
      51              :     oauth_free,
      52              : };
      53              : 
      54              : /*
      55              :  * Initializes mechanism state for OAUTHBEARER.
      56              :  *
      57              :  * For a full description of the API, see libpq/fe-auth-sasl.h.
      58              :  */
      59              : static void *
      60            0 : oauth_init(PGconn *conn, const char *password,
      61              :            const char *sasl_mechanism)
      62              : {
      63              :     fe_oauth_state *state;
      64              : 
      65              :     /*
      66              :      * We only support one SASL mechanism here; anything else is programmer
      67              :      * error.
      68              :      */
      69              :     Assert(sasl_mechanism != NULL);
      70              :     Assert(strcmp(sasl_mechanism, OAUTHBEARER_NAME) == 0);
      71              : 
      72            0 :     state = calloc(1, sizeof(*state));
      73            0 :     if (!state)
      74            0 :         return NULL;
      75              : 
      76            0 :     state->step = FE_OAUTH_INIT;
      77            0 :     state->conn = conn;
      78              : 
      79            0 :     return state;
      80              : }
      81              : 
      82              : /*
      83              :  * Frees the state allocated by oauth_init().
      84              :  *
      85              :  * This handles only mechanism state tied to the connection lifetime; state
      86              :  * stored in state->async_ctx is freed up either immediately after the
      87              :  * authentication handshake succeeds, or before the mechanism is cleaned up on
      88              :  * failure. See pg_fe_cleanup_oauth_flow() and cleanup_oauth_flow().
      89              :  */
      90              : static void
      91            0 : oauth_free(void *opaq)
      92              : {
      93            0 :     fe_oauth_state *state = opaq;
      94              : 
      95              :     /* Any async authentication state should have been cleaned up already. */
      96              :     Assert(!state->async_ctx);
      97              : 
      98            0 :     free(state);
      99            0 : }
     100              : 
     101              : #define kvsep "\x01"
     102              : 
     103              : /*
     104              :  * Constructs an OAUTHBEARER client initial response (RFC 7628, Sec. 3.1).
     105              :  *
     106              :  * If discover is true, the initial response will contain a request for the
     107              :  * server's required OAuth parameters (Sec. 4.3). Otherwise, conn->token must
     108              :  * be set; it will be sent as the connection's bearer token.
     109              :  *
     110              :  * Returns the response as a null-terminated string, or NULL on error.
     111              :  */
     112              : static char *
     113            0 : client_initial_response(PGconn *conn, bool discover)
     114              : {
     115              :     static const char *const resp_format = "n,," kvsep "auth=%s%s" kvsep kvsep;
     116              : 
     117              :     PQExpBufferData buf;
     118              :     const char *authn_scheme;
     119            0 :     char       *response = NULL;
     120            0 :     const char *token = conn->oauth_token;
     121              : 
     122            0 :     if (discover)
     123              :     {
     124              :         /* Parameter discovery uses a completely empty auth value. */
     125            0 :         authn_scheme = token = "";
     126              :     }
     127              :     else
     128              :     {
     129              :         /*
     130              :          * Use a Bearer authentication scheme (RFC 6750, Sec. 2.1). A trailing
     131              :          * space is used as a separator.
     132              :          */
     133            0 :         authn_scheme = "Bearer ";
     134              : 
     135              :         /* conn->token must have been set in this case. */
     136            0 :         if (!token)
     137              :         {
     138              :             Assert(false);
     139            0 :             libpq_append_conn_error(conn,
     140              :                                     "internal error: no OAuth token was set for the connection");
     141            0 :             return NULL;
     142              :         }
     143              :     }
     144              : 
     145            0 :     initPQExpBuffer(&buf);
     146            0 :     appendPQExpBuffer(&buf, resp_format, authn_scheme, token);
     147              : 
     148            0 :     if (!PQExpBufferDataBroken(buf))
     149            0 :         response = strdup(buf.data);
     150            0 :     termPQExpBuffer(&buf);
     151              : 
     152            0 :     if (!response)
     153            0 :         libpq_append_conn_error(conn, "out of memory");
     154              : 
     155            0 :     return response;
     156              : }
     157              : 
     158              : /*
     159              :  * JSON Parser (for the OAUTHBEARER error result)
     160              :  */
     161              : 
     162              : /* Relevant JSON fields in the error result object. */
     163              : #define ERROR_STATUS_FIELD "status"
     164              : #define ERROR_SCOPE_FIELD "scope"
     165              : #define ERROR_OPENID_CONFIGURATION_FIELD "openid-configuration"
     166              : 
     167              : /*
     168              :  * Limit the maximum number of nested objects/arrays. Because OAUTHBEARER
     169              :  * doesn't have any defined extensions for its JSON yet, we can be much more
     170              :  * conservative here than with libpq-oauth's MAX_OAUTH_NESTING_LEVEL; we expect
     171              :  * a nesting level of 1 in practice.
     172              :  */
     173              : #define MAX_SASL_NESTING_LEVEL 8
     174              : 
     175              : struct json_ctx
     176              : {
     177              :     char       *errmsg;         /* any non-NULL value stops all processing */
     178              :     PQExpBufferData errbuf;     /* backing memory for errmsg */
     179              :     int         nested;         /* nesting level (zero is the top) */
     180              : 
     181              :     const char *target_field_name;  /* points to a static allocation */
     182              :     char      **target_field;   /* see below */
     183              : 
     184              :     /* target_field, if set, points to one of the following: */
     185              :     char       *status;
     186              :     char       *scope;
     187              :     char       *discovery_uri;
     188              : };
     189              : 
     190              : #define oauth_json_has_error(ctx) \
     191              :     (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)
     192              : 
     193              : #define oauth_json_set_error(ctx, fmt, ...) \
     194              :     do { \
     195              :         appendPQExpBuffer(&(ctx)->errbuf, libpq_gettext(fmt), ##__VA_ARGS__); \
     196              :         (ctx)->errmsg = (ctx)->errbuf.data; \
     197              :     } while (0)
     198              : 
     199              : /* An untranslated version of oauth_json_set_error(). */
     200              : #define oauth_json_set_error_internal(ctx, ...) \
     201              :     do { \
     202              :         appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
     203              :         (ctx)->errmsg = (ctx)->errbuf.data; \
     204              :     } while (0)
     205              : 
     206              : static JsonParseErrorType
     207            0 : oauth_json_object_start(void *state)
     208              : {
     209            0 :     struct json_ctx *ctx = state;
     210              : 
     211            0 :     if (ctx->target_field)
     212              :     {
     213              :         Assert(ctx->nested == 1);
     214              : 
     215            0 :         oauth_json_set_error(ctx,
     216              :                              "field \"%s\" must be a string",
     217              :                              ctx->target_field_name);
     218              :     }
     219              : 
     220            0 :     ++ctx->nested;
     221            0 :     if (ctx->nested > MAX_SASL_NESTING_LEVEL)
     222            0 :         oauth_json_set_error(ctx, "JSON is too deeply nested");
     223              : 
     224            0 :     return oauth_json_has_error(ctx) ? JSON_SEM_ACTION_FAILED : JSON_SUCCESS;
     225              : }
     226              : 
     227              : static JsonParseErrorType
     228            0 : oauth_json_object_end(void *state)
     229              : {
     230            0 :     struct json_ctx *ctx = state;
     231              : 
     232            0 :     --ctx->nested;
     233            0 :     return JSON_SUCCESS;
     234              : }
     235              : 
     236              : static JsonParseErrorType
     237            0 : oauth_json_object_field_start(void *state, char *name, bool isnull)
     238              : {
     239            0 :     struct json_ctx *ctx = state;
     240              : 
     241              :     /* Only top-level keys are considered. */
     242            0 :     if (ctx->nested == 1)
     243              :     {
     244            0 :         if (strcmp(name, ERROR_STATUS_FIELD) == 0)
     245              :         {
     246            0 :             ctx->target_field_name = ERROR_STATUS_FIELD;
     247            0 :             ctx->target_field = &ctx->status;
     248              :         }
     249            0 :         else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
     250              :         {
     251            0 :             ctx->target_field_name = ERROR_SCOPE_FIELD;
     252            0 :             ctx->target_field = &ctx->scope;
     253              :         }
     254            0 :         else if (strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD) == 0)
     255              :         {
     256            0 :             ctx->target_field_name = ERROR_OPENID_CONFIGURATION_FIELD;
     257            0 :             ctx->target_field = &ctx->discovery_uri;
     258              :         }
     259              :     }
     260              : 
     261            0 :     return JSON_SUCCESS;
     262              : }
     263              : 
     264              : static JsonParseErrorType
     265            0 : oauth_json_array_start(void *state)
     266              : {
     267            0 :     struct json_ctx *ctx = state;
     268              : 
     269            0 :     if (!ctx->nested)
     270              :     {
     271            0 :         oauth_json_set_error(ctx, "top-level element must be an object");
     272              :     }
     273            0 :     else if (ctx->target_field)
     274              :     {
     275              :         Assert(ctx->nested == 1);
     276              : 
     277            0 :         oauth_json_set_error(ctx,
     278              :                              "field \"%s\" must be a string",
     279              :                              ctx->target_field_name);
     280              :     }
     281              : 
     282            0 :     ++ctx->nested;
     283            0 :     if (ctx->nested > MAX_SASL_NESTING_LEVEL)
     284            0 :         oauth_json_set_error(ctx, "JSON is too deeply nested");
     285              : 
     286            0 :     return oauth_json_has_error(ctx) ? JSON_SEM_ACTION_FAILED : JSON_SUCCESS;
     287              : }
     288              : 
     289              : static JsonParseErrorType
     290            0 : oauth_json_array_end(void *state)
     291              : {
     292            0 :     struct json_ctx *ctx = state;
     293              : 
     294            0 :     --ctx->nested;
     295            0 :     return JSON_SUCCESS;
     296              : }
     297              : 
     298              : static JsonParseErrorType
     299            0 : oauth_json_scalar(void *state, char *token, JsonTokenType type)
     300              : {
     301            0 :     struct json_ctx *ctx = state;
     302              : 
     303            0 :     if (!ctx->nested)
     304              :     {
     305            0 :         oauth_json_set_error(ctx, "top-level element must be an object");
     306            0 :         return JSON_SEM_ACTION_FAILED;
     307              :     }
     308              : 
     309            0 :     if (ctx->target_field)
     310              :     {
     311            0 :         if (ctx->nested != 1)
     312              :         {
     313              :             /*
     314              :              * ctx->target_field should not have been set for nested keys.
     315              :              * Assert and don't continue any further for production builds.
     316              :              */
     317              :             Assert(false);
     318            0 :             oauth_json_set_error_internal(ctx,
     319              :                                           "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
     320              :                                           ctx->nested);
     321            0 :             return JSON_SEM_ACTION_FAILED;
     322              :         }
     323              : 
     324              :         /*
     325              :          * We don't allow duplicate field names; error out if the target has
     326              :          * already been set.
     327              :          */
     328            0 :         if (*ctx->target_field)
     329              :         {
     330            0 :             oauth_json_set_error(ctx,
     331              :                                  "field \"%s\" is duplicated",
     332              :                                  ctx->target_field_name);
     333            0 :             return JSON_SEM_ACTION_FAILED;
     334              :         }
     335              : 
     336              :         /* The only fields we support are strings. */
     337            0 :         if (type != JSON_TOKEN_STRING)
     338              :         {
     339            0 :             oauth_json_set_error(ctx,
     340              :                                  "field \"%s\" must be a string",
     341              :                                  ctx->target_field_name);
     342            0 :             return JSON_SEM_ACTION_FAILED;
     343              :         }
     344              : 
     345            0 :         *ctx->target_field = strdup(token);
     346            0 :         if (!*ctx->target_field)
     347            0 :             return JSON_OUT_OF_MEMORY;
     348              : 
     349            0 :         ctx->target_field = NULL;
     350            0 :         ctx->target_field_name = NULL;
     351              :     }
     352              :     else
     353              :     {
     354              :         /* otherwise we just ignore it */
     355              :     }
     356              : 
     357            0 :     return JSON_SUCCESS;
     358              : }
     359              : 
     360              : #define HTTPS_SCHEME "https://"
     361              : #define HTTP_SCHEME "http://"
     362              : 
     363              : /* We support both well-known suffixes defined by RFC 8414. */
     364              : #define WK_PREFIX "/.well-known/"
     365              : #define OPENID_WK_SUFFIX "openid-configuration"
     366              : #define OAUTH_WK_SUFFIX "oauth-authorization-server"
     367              : 
     368              : /*
     369              :  * Derives an issuer identifier from one of our recognized .well-known URIs,
     370              :  * using the rules in RFC 8414.
     371              :  */
     372              : static char *
     373            0 : issuer_from_well_known_uri(PGconn *conn, const char *wkuri)
     374              : {
     375            0 :     const char *authority_start = NULL;
     376              :     const char *wk_start;
     377              :     const char *wk_end;
     378              :     char       *issuer;
     379              :     ptrdiff_t   start_offset,
     380              :                 end_offset;
     381              :     size_t      end_len;
     382              : 
     383              :     /*
     384              :      * https:// is required for issuer identifiers (RFC 8414, Sec. 2; OIDC
     385              :      * Discovery 1.0, Sec. 3). This is a case-insensitive comparison at this
     386              :      * level (but issuer identifier comparison at the level above this is
     387              :      * case-sensitive, so in practice it's probably moot).
     388              :      */
     389            0 :     if (pg_strncasecmp(wkuri, HTTPS_SCHEME, strlen(HTTPS_SCHEME)) == 0)
     390            0 :         authority_start = wkuri + strlen(HTTPS_SCHEME);
     391              : 
     392            0 :     if (!authority_start
     393            0 :         && (oauth_parse_debug_flags() & OAUTHDEBUG_UNSAFE_HTTP)
     394            0 :         && pg_strncasecmp(wkuri, HTTP_SCHEME, strlen(HTTP_SCHEME)) == 0)
     395              :     {
     396              :         /* Allow http:// for testing only. */
     397            0 :         authority_start = wkuri + strlen(HTTP_SCHEME);
     398              :     }
     399              : 
     400            0 :     if (!authority_start)
     401              :     {
     402            0 :         libpq_append_conn_error(conn,
     403              :                                 "OAuth discovery URI \"%s\" must use HTTPS",
     404              :                                 wkuri);
     405            0 :         return NULL;
     406              :     }
     407              : 
     408              :     /*
     409              :      * Well-known URIs in general may support queries and fragments, but the
     410              :      * two types we support here do not. (They must be constructed from the
     411              :      * components of issuer identifiers, which themselves may not contain any
     412              :      * queries or fragments.)
     413              :      *
     414              :      * It's important to check this first, to avoid getting tricked later by a
     415              :      * prefix buried inside a query or fragment.
     416              :      */
     417            0 :     if (strpbrk(authority_start, "?#") != NULL)
     418              :     {
     419            0 :         libpq_append_conn_error(conn,
     420              :                                 "OAuth discovery URI \"%s\" must not contain query or fragment components",
     421              :                                 wkuri);
     422            0 :         return NULL;
     423              :     }
     424              : 
     425              :     /*
     426              :      * Find the start of the .well-known prefix. IETF rules (RFC 8615) state
     427              :      * this must be at the beginning of the path component, but OIDC defined
     428              :      * it at the end instead (OIDC Discovery 1.0, Sec. 4), so we have to
     429              :      * search for it anywhere.
     430              :      */
     431            0 :     wk_start = strstr(authority_start, WK_PREFIX);
     432            0 :     if (!wk_start)
     433              :     {
     434            0 :         libpq_append_conn_error(conn,
     435              :                                 "OAuth discovery URI \"%s\" is not a .well-known URI",
     436              :                                 wkuri);
     437            0 :         return NULL;
     438              :     }
     439              : 
     440              :     /*
     441              :      * Now find the suffix type. We only support the two defined in OIDC
     442              :      * Discovery 1.0 and RFC 8414.
     443              :      */
     444            0 :     wk_end = wk_start + strlen(WK_PREFIX);
     445              : 
     446            0 :     if (strncmp(wk_end, OPENID_WK_SUFFIX, strlen(OPENID_WK_SUFFIX)) == 0)
     447            0 :         wk_end += strlen(OPENID_WK_SUFFIX);
     448            0 :     else if (strncmp(wk_end, OAUTH_WK_SUFFIX, strlen(OAUTH_WK_SUFFIX)) == 0)
     449            0 :         wk_end += strlen(OAUTH_WK_SUFFIX);
     450              :     else
     451            0 :         wk_end = NULL;
     452              : 
     453              :     /*
     454              :      * Even if there's a match, we still need to check to make sure the suffix
     455              :      * takes up the entire path segment, to weed out constructions like
     456              :      * "/.well-known/openid-configuration-bad".
     457              :      */
     458            0 :     if (!wk_end || (*wk_end != '/' && *wk_end != '\0'))
     459              :     {
     460            0 :         libpq_append_conn_error(conn,
     461              :                                 "OAuth discovery URI \"%s\" uses an unsupported .well-known suffix",
     462              :                                 wkuri);
     463            0 :         return NULL;
     464              :     }
     465              : 
     466              :     /*
     467              :      * Finally, make sure the .well-known components are provided either as a
     468              :      * prefix (IETF style) or as a postfix (OIDC style). In other words,
     469              :      * "https://localhost/a/.well-known/openid-configuration/b" is not allowed
     470              :      * to claim association with "https://localhost/a/b".
     471              :      */
     472            0 :     if (*wk_end != '\0')
     473              :     {
     474              :         /*
     475              :          * It's not at the end, so it's required to be at the beginning at the
     476              :          * path. Find the starting slash.
     477              :          */
     478              :         const char *path_start;
     479              : 
     480            0 :         path_start = strchr(authority_start, '/');
     481              :         Assert(path_start);     /* otherwise we wouldn't have found WK_PREFIX */
     482              : 
     483            0 :         if (wk_start != path_start)
     484              :         {
     485            0 :             libpq_append_conn_error(conn,
     486              :                                     "OAuth discovery URI \"%s\" uses an invalid format",
     487              :                                     wkuri);
     488            0 :             return NULL;
     489              :         }
     490              :     }
     491              : 
     492              :     /* Checks passed! Now build the issuer. */
     493            0 :     issuer = strdup(wkuri);
     494            0 :     if (!issuer)
     495              :     {
     496            0 :         libpq_append_conn_error(conn, "out of memory");
     497            0 :         return NULL;
     498              :     }
     499              : 
     500              :     /*
     501              :      * The .well-known components are from [wk_start, wk_end). Remove those to
     502              :      * form the issuer ID, by shifting the path suffix (which may be empty)
     503              :      * leftwards.
     504              :      */
     505            0 :     start_offset = wk_start - wkuri;
     506            0 :     end_offset = wk_end - wkuri;
     507            0 :     end_len = strlen(wk_end) + 1;   /* move the NULL terminator too */
     508              : 
     509            0 :     memmove(issuer + start_offset, issuer + end_offset, end_len);
     510              : 
     511            0 :     return issuer;
     512              : }
     513              : 
     514              : /*
     515              :  * Parses the server error result (RFC 7628, Sec. 3.2.2) contained in msg and
     516              :  * stores any discovered openid_configuration and scope settings for the
     517              :  * connection.
     518              :  */
     519              : static bool
     520            0 : handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
     521              : {
     522              :     JsonLexContext *lex;
     523            0 :     JsonSemAction sem = {0};
     524              :     JsonParseErrorType err;
     525            0 :     struct json_ctx ctx = {0};
     526            0 :     char       *errmsg = NULL;
     527            0 :     bool        success = false;
     528              : 
     529              :     Assert(conn->oauth_issuer_id);   /* ensured by setup_oauth_parameters() */
     530              : 
     531              :     /* Sanity check. */
     532            0 :     if (strlen(msg) != msglen)
     533              :     {
     534            0 :         libpq_append_conn_error(conn,
     535              :                                 "server's error message contained an embedded NULL, and was discarded");
     536            0 :         return false;
     537              :     }
     538              : 
     539              :     /*
     540              :      * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
     541              :      * that up front.
     542              :      */
     543            0 :     if (pg_encoding_verifymbstr(PG_UTF8, msg, msglen) != msglen)
     544              :     {
     545            0 :         libpq_append_conn_error(conn,
     546              :                                 "server's error response is not valid UTF-8");
     547            0 :         return false;
     548              :     }
     549              : 
     550            0 :     lex = makeJsonLexContextCstringLen(NULL, msg, msglen, PG_UTF8, true);
     551            0 :     setJsonLexContextOwnsTokens(lex, true); /* must not leak on error */
     552              : 
     553            0 :     initPQExpBuffer(&ctx.errbuf);
     554            0 :     sem.semstate = &ctx;
     555              : 
     556            0 :     sem.object_start = oauth_json_object_start;
     557            0 :     sem.object_end = oauth_json_object_end;
     558            0 :     sem.object_field_start = oauth_json_object_field_start;
     559            0 :     sem.array_start = oauth_json_array_start;
     560            0 :     sem.array_end = oauth_json_array_end;
     561            0 :     sem.scalar = oauth_json_scalar;
     562              : 
     563            0 :     err = pg_parse_json(lex, &sem);
     564              : 
     565            0 :     if (err == JSON_SEM_ACTION_FAILED)
     566              :     {
     567            0 :         if (PQExpBufferDataBroken(ctx.errbuf))
     568            0 :             errmsg = libpq_gettext("out of memory");
     569            0 :         else if (ctx.errmsg)
     570            0 :             errmsg = ctx.errmsg;
     571              :         else
     572              :         {
     573              :             /*
     574              :              * Developer error: one of the action callbacks didn't call
     575              :              * oauth_json_set_error() before erroring out.
     576              :              */
     577              :             Assert(oauth_json_has_error(&ctx));
     578            0 :             errmsg = "<unexpected empty error>";
     579              :         }
     580              :     }
     581            0 :     else if (err != JSON_SUCCESS)
     582            0 :         errmsg = json_errdetail(err, lex);
     583              : 
     584            0 :     if (errmsg)
     585            0 :         libpq_append_conn_error(conn,
     586              :                                 "failed to parse server's error response: %s",
     587              :                                 errmsg);
     588              : 
     589              :     /* Don't need the error buffer or the JSON lexer anymore. */
     590            0 :     termPQExpBuffer(&ctx.errbuf);
     591            0 :     freeJsonLexContext(lex);
     592              : 
     593            0 :     if (errmsg)
     594            0 :         goto cleanup;
     595              : 
     596            0 :     if (ctx.discovery_uri)
     597              :     {
     598              :         char       *discovery_issuer;
     599              : 
     600              :         /*
     601              :          * The URI MUST correspond to our existing issuer, to avoid mix-ups.
     602              :          *
     603              :          * Issuer comparison is done byte-wise, rather than performing any URL
     604              :          * normalization; this follows the suggestions for issuer comparison
     605              :          * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
     606              :          * vastly simplifies things. Since this is the key protection against
     607              :          * a rogue server sending the client to an untrustworthy location,
     608              :          * simpler is better.
     609              :          */
     610            0 :         discovery_issuer = issuer_from_well_known_uri(conn, ctx.discovery_uri);
     611            0 :         if (!discovery_issuer)
     612            0 :             goto cleanup;       /* error message already set */
     613              : 
     614            0 :         if (strcmp(conn->oauth_issuer_id, discovery_issuer) != 0)
     615              :         {
     616            0 :             libpq_append_conn_error(conn,
     617              :                                     "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
     618              :                                     ctx.discovery_uri, discovery_issuer,
     619              :                                     conn->oauth_issuer_id);
     620              : 
     621            0 :             free(discovery_issuer);
     622            0 :             goto cleanup;
     623              :         }
     624              : 
     625            0 :         free(discovery_issuer);
     626              : 
     627            0 :         if (!conn->oauth_discovery_uri)
     628              :         {
     629            0 :             conn->oauth_discovery_uri = ctx.discovery_uri;
     630            0 :             ctx.discovery_uri = NULL;
     631              :         }
     632              :         else
     633              :         {
     634              :             /* This must match the URI we'd previously determined. */
     635            0 :             if (strcmp(conn->oauth_discovery_uri, ctx.discovery_uri) != 0)
     636              :             {
     637            0 :                 libpq_append_conn_error(conn,
     638              :                                         "server's discovery document has moved to %s (previous location was %s)",
     639              :                                         ctx.discovery_uri,
     640              :                                         conn->oauth_discovery_uri);
     641            0 :                 goto cleanup;
     642              :             }
     643              :         }
     644              :     }
     645              : 
     646            0 :     if (ctx.scope)
     647              :     {
     648              :         /* Servers may not override a previously set oauth_scope. */
     649            0 :         if (!conn->oauth_scope)
     650              :         {
     651            0 :             conn->oauth_scope = ctx.scope;
     652            0 :             ctx.scope = NULL;
     653              :         }
     654              :     }
     655              : 
     656            0 :     if (!ctx.status)
     657              :     {
     658            0 :         libpq_append_conn_error(conn,
     659              :                                 "server sent error response without a status");
     660            0 :         goto cleanup;
     661              :     }
     662              : 
     663            0 :     if (strcmp(ctx.status, "invalid_token") != 0)
     664              :     {
     665              :         /*
     666              :          * invalid_token is the only error code we'll automatically retry for;
     667              :          * otherwise, just bail out now.
     668              :          */
     669            0 :         libpq_append_conn_error(conn,
     670              :                                 "server rejected OAuth bearer token: %s",
     671              :                                 ctx.status);
     672            0 :         goto cleanup;
     673              :     }
     674              : 
     675            0 :     success = true;
     676              : 
     677            0 : cleanup:
     678            0 :     free(ctx.status);
     679            0 :     free(ctx.scope);
     680            0 :     free(ctx.discovery_uri);
     681              : 
     682            0 :     return success;
     683              : }
     684              : 
     685              : /*
     686              :  * Helper for handling flow failures. If anything was put into request->error,
     687              :  * it's added to conn->errorMessage here.
     688              :  */
     689              : static void
     690            0 : report_flow_error(PGconn *conn, const PGoauthBearerRequestV2 *request)
     691              : {
     692            0 :     fe_oauth_state *state = conn->sasl_state;
     693            0 :     const char *errmsg = request->error;
     694              : 
     695              :     /*
     696              :      * User-defined flows are called out explicitly so that the user knows who
     697              :      * to blame. Builtin flows don't need that extra message length; we expect
     698              :      * them to always fill in request->error on failure anyway.
     699              :      */
     700            0 :     if (state->builtin)
     701              :     {
     702            0 :         if (!errmsg)
     703              :         {
     704              :             /*
     705              :              * Don't turn a bug here into a crash in production, but don't
     706              :              * bother translating either.
     707              :              */
     708              :             Assert(false);
     709            0 :             errmsg = "builtin flow failed but did not provide an error message";
     710              :         }
     711              : 
     712            0 :         appendPQExpBufferStr(&conn->errorMessage, errmsg);
     713              :     }
     714              :     else
     715              :     {
     716            0 :         appendPQExpBufferStr(&conn->errorMessage,
     717            0 :                              libpq_gettext("user-defined OAuth flow failed"));
     718            0 :         if (errmsg)
     719              :         {
     720            0 :             appendPQExpBufferStr(&conn->errorMessage, ": ");
     721            0 :             appendPQExpBufferStr(&conn->errorMessage, errmsg);
     722              :         }
     723              :     }
     724              : 
     725            0 :     appendPQExpBufferChar(&conn->errorMessage, '\n');
     726            0 : }
     727              : 
     728              : /*
     729              :  * Callback implementation of conn->async_auth() for OAuth flows. Delegates the
     730              :  * retrieval of the token to the PGoauthBearerRequestV2.async() callback.
     731              :  *
     732              :  * This will be called multiple times as needed; the callback is responsible for
     733              :  * setting an altsock to signal and returning the correct PGRES_POLLING_*
     734              :  * statuses for use by PQconnectPoll().
     735              :  */
     736              : static PostgresPollingStatusType
     737            0 : run_oauth_flow(PGconn *conn)
     738              : {
     739            0 :     fe_oauth_state *state = conn->sasl_state;
     740            0 :     PGoauthBearerRequestV2 *request = state->async_ctx;
     741              :     PostgresPollingStatusType status;
     742              : 
     743            0 :     if (!request->v1.async)
     744              :     {
     745              :         Assert(!state->builtin); /* be very noisy if our code does this */
     746            0 :         libpq_append_conn_error(conn,
     747              :                                 "user-defined OAuth flow provided neither a token nor an async callback");
     748            0 :         return PGRES_POLLING_FAILED;
     749              :     }
     750              : 
     751            0 :     status = do_async(state, request);
     752              : 
     753            0 :     if (status == PGRES_POLLING_FAILED)
     754              :     {
     755            0 :         report_flow_error(conn, request);
     756            0 :         return status;
     757              :     }
     758            0 :     else if (status == PGRES_POLLING_OK)
     759              :     {
     760              :         /*
     761              :          * We already have a token, so copy it into the conn. (We can't hold
     762              :          * onto the original string, since it may not be safe for us to free()
     763              :          * it.)
     764              :          */
     765            0 :         if (!request->v1.token)
     766              :         {
     767              :             Assert(!state->builtin);
     768            0 :             libpq_append_conn_error(conn,
     769              :                                     "user-defined OAuth flow did not provide a token");
     770            0 :             return PGRES_POLLING_FAILED;
     771              :         }
     772              : 
     773            0 :         conn->oauth_token = strdup(request->v1.token);
     774            0 :         if (!conn->oauth_token)
     775              :         {
     776            0 :             libpq_append_conn_error(conn, "out of memory");
     777            0 :             return PGRES_POLLING_FAILED;
     778              :         }
     779              : 
     780            0 :         return PGRES_POLLING_OK;
     781              :     }
     782              : 
     783              :     /* The hook wants the client to poll the altsock. Make sure it set one. */
     784            0 :     if (conn->altsock == PGINVALID_SOCKET)
     785              :     {
     786              :         Assert(!state->builtin);
     787            0 :         libpq_append_conn_error(conn,
     788              :                                 "user-defined OAuth flow did not provide a socket for polling");
     789            0 :         return PGRES_POLLING_FAILED;
     790              :     }
     791              : 
     792            0 :     return status;
     793              : }
     794              : 
     795              : /*
     796              :  * Cleanup callback for the async flow. Delegates most of its job to
     797              :  * PGoauthBearerRequest.cleanup(), then disconnects the altsock and frees the
     798              :  * request itself.
     799              :  *
     800              :  * This is called either at the end of a successful authentication, or during
     801              :  * pqDropConnection(), so we won't leak resources even if PQconnectPoll() never
     802              :  * calls us back.
     803              :  */
     804              : static void
     805            0 : cleanup_oauth_flow(PGconn *conn)
     806              : {
     807            0 :     fe_oauth_state *state = conn->sasl_state;
     808            0 :     PGoauthBearerRequestV2 *request = state->async_ctx;
     809              : 
     810              :     Assert(request);
     811              : 
     812            0 :     do_cleanup(state, request);
     813            0 :     conn->altsock = PGINVALID_SOCKET;
     814              : 
     815            0 :     free(request);
     816            0 :     state->async_ctx = NULL;
     817            0 : }
     818              : 
     819              : /*-------------
     820              :  * Builtin Flow
     821              :  *
     822              :  * There are three potential implementations of use_builtin_flow:
     823              :  *
     824              :  * 1) If the OAuth client is disabled at configuration time, return zero.
     825              :  *    Dependent clients must provide their own flow.
     826              :  * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
     827              :  *    the libpq-oauth plugin and use its implementation.
     828              :  * 3) Otherwise, use flow callbacks that are statically linked into the
     829              :  *    executable.
     830              :  *
     831              :  * For caller convenience, the return value follows the convention of
     832              :  * PQauthDataHook: zero means no implementation is provided, negative indicates
     833              :  * failure, and positive indicates success.
     834              :  */
     835              : 
     836              : #if !defined(USE_LIBCURL)
     837              : 
     838              : /*
     839              :  * This configuration doesn't support the builtin flow.
     840              :  */
     841              : 
     842              : static int
     843            0 : use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
     844              : {
     845            0 :     return 0;
     846              : }
     847              : 
     848              : #elif defined(USE_DYNAMIC_OAUTH)
     849              : 
     850              : /*
     851              :  * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
     852              :  */
     853              : 
     854              : typedef char *(*libpq_gettext_func) (const char *msgid);
     855              : 
     856              : /*
     857              :  * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
     858              :  * callbacks into the connection's async auth handlers.
     859              :  *
     860              :  * Failure to load here results in a relatively quiet connection error, to
     861              :  * handle the use case where the build supports loading a flow but a user does
     862              :  * not want to install it. Troubleshooting of linker/loader failures can be done
     863              :  * via PGOAUTHDEBUG.
     864              :  *
     865              :  * The lifetime of *request ends shortly after this call, so it must be copied
     866              :  * to longer-lived storage.
     867              :  */
     868              : static int
     869              : use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
     870              : {
     871              :     static bool initialized = false;
     872              :     static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
     873              :     int         lockerr;
     874              : 
     875              :     void        (*init) (libpq_gettext_func gettext_impl);
     876              :     int         (*start_flow) (PGconn *conn, PGoauthBearerRequestV2 *request);
     877              : 
     878              :     /*
     879              :      * On macOS only, load the module using its absolute install path; the
     880              :      * standard search behavior is not very helpful for this use case. Unlike
     881              :      * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
     882              :      * absolute paths (modulo SIP effects), so tests can continue to work.
     883              :      *
     884              :      * On the other platforms, load the module using only the basename, to
     885              :      * rely on the runtime linker's standard search behavior.
     886              :      */
     887              :     const char *const module_name =
     888              : #if defined(__darwin__)
     889              :         LIBDIR "/libpq-oauth" DLSUFFIX;
     890              : #else
     891              :         "libpq-oauth" DLSUFFIX;
     892              : #endif
     893              : 
     894              :     state->flow_module = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
     895              :     if (!state->flow_module)
     896              :     {
     897              :         /*
     898              :          * For end users, this probably isn't an error condition, it just
     899              :          * means the flow isn't installed. Developers and package maintainers
     900              :          * may want to debug this via the PGOAUTHDEBUG envvar, though.
     901              :          *
     902              :          * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
     903              :          */
     904              :         if (oauth_parse_debug_flags() & OAUTHDEBUG_PLUGIN_ERRORS)
     905              :             fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
     906              : 
     907              :         return 0;
     908              :     }
     909              : 
     910              :     /*
     911              :      * Our libpq-oauth.so provides a special initialization function for libpq
     912              :      * integration. If we don't find this, assume that a custom module is in
     913              :      * use instead.
     914              :      */
     915              :     init = dlsym(state->flow_module, "libpq_oauth_init");
     916              :     if (!init)
     917              :         state->builtin = false; /* adjust our error messages */
     918              : 
     919              :     if ((start_flow = dlsym(state->flow_module, "pg_start_oauthbearer")) == NULL)
     920              :     {
     921              :         /*
     922              :          * This is more of an error condition than the one above, but the
     923              :          * cause is still locked behind PGOAUTHDEBUG due to the dlerror()
     924              :          * threadsafety issue.
     925              :          */
     926              :         if (oauth_parse_debug_flags() & OAUTHDEBUG_PLUGIN_ERRORS)
     927              :             fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
     928              : 
     929              :         dlclose(state->flow_module);
     930              :         state->flow_module = NULL;
     931              : 
     932              :         request->error = libpq_gettext("could not find entry point for libpq-oauth");
     933              :         return -1;
     934              :     }
     935              : 
     936              :     /*
     937              :      * Past this point, we do not unload the module. It stays in the process
     938              :      * permanently.
     939              :      */
     940              : 
     941              :     if (init)
     942              :     {
     943              :         /*
     944              :          * We need to inject necessary function pointers into the module. This
     945              :          * only needs to be done once -- even if the pointers are constant,
     946              :          * assigning them while another thread is executing the flows feels
     947              :          * like tempting fate.
     948              :          */
     949              :         if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
     950              :         {
     951              :             /* Should not happen... but don't continue if it does. */
     952              :             Assert(false);
     953              : 
     954              :             appendPQExpBuffer(&conn->errorMessage,
     955              :                               "use_builtin_flow: failed to lock mutex (%d)\n",
     956              :                               lockerr);
     957              : 
     958              :             request->error = "";   /* satisfy report_flow_error() */
     959              :             return -1;
     960              :         }
     961              : 
     962              :         if (!initialized)
     963              :         {
     964              :             init(
     965              : #ifdef ENABLE_NLS
     966              :                  libpq_gettext
     967              : #else
     968              :                  NULL
     969              : #endif
     970              :                 );
     971              : 
     972              :             initialized = true;
     973              :         }
     974              : 
     975              :         pthread_mutex_unlock(&init_mutex);
     976              :     }
     977              : 
     978              :     return (start_flow(conn, request) == 0) ? 1 : -1;
     979              : }
     980              : 
     981              : #else
     982              : 
     983              : /*
     984              :  * For static builds, we can just call pg_start_oauthbearer() directly. It's
     985              :  * provided by libpq-oauth.a.
     986              :  */
     987              : 
     988              : extern int  pg_start_oauthbearer(PGconn *conn, PGoauthBearerRequestV2 *request);
     989              : 
     990              : static int
     991              : use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
     992              : {
     993              :     return (pg_start_oauthbearer(conn, request) == 0) ? 1 : -1;
     994              : }
     995              : 
     996              : #endif                          /* USE_LIBCURL */
     997              : 
     998              : 
     999              : /*
    1000              :  * Chooses an OAuth client flow for the connection, which will retrieve a Bearer
    1001              :  * token for presentation to the server.
    1002              :  *
    1003              :  * If the application has registered a custom flow handler using
    1004              :  * PQAUTHDATA_OAUTH_BEARER_TOKEN[_V2], it may either return a token immediately
    1005              :  * (e.g. if it has one cached for immediate use), or set up for a series of
    1006              :  * asynchronous callbacks which will be managed by run_oauth_flow().
    1007              :  *
    1008              :  * If the default handler is used instead, a Device Authorization flow is used
    1009              :  * for the connection if support has been compiled in. (See oauth-curl.c for
    1010              :  * implementation details.)
    1011              :  *
    1012              :  * If neither a custom handler nor the builtin flow is available, the connection
    1013              :  * fails here.
    1014              :  */
    1015              : static bool
    1016            0 : setup_token_request(PGconn *conn, fe_oauth_state *state)
    1017              : {
    1018              :     int         res;
    1019            0 :     PGoauthBearerRequestV2 request = {
    1020              :         .v1 = {
    1021            0 :             .openid_configuration = conn->oauth_discovery_uri,
    1022            0 :             .scope = conn->oauth_scope,
    1023              :         },
    1024            0 :         .issuer = conn->oauth_issuer_id,
    1025              :     };
    1026              : 
    1027              :     Assert(request.v1.openid_configuration);
    1028              :     Assert(request.issuer);
    1029              : 
    1030              :     /*
    1031              :      * The client may have overridden the OAuth flow. Try the v2 hook first,
    1032              :      * then fall back to the v1 implementation. If neither is available, try
    1033              :      * the builtin flow.
    1034              :      */
    1035            0 :     res = PQauthDataHook(PQAUTHDATA_OAUTH_BEARER_TOKEN_V2, conn, &request);
    1036            0 :     if (res == 0)
    1037              :     {
    1038            0 :         poison_req_v2(&request, true);
    1039              : 
    1040            0 :         res = PQauthDataHook(PQAUTHDATA_OAUTH_BEARER_TOKEN, conn, &request);
    1041            0 :         state->v1 = (res != 0);
    1042              : 
    1043            0 :         poison_req_v2(&request, false);
    1044              :     }
    1045            0 :     if (res == 0)
    1046              :     {
    1047            0 :         state->builtin = true;
    1048            0 :         res = use_builtin_flow(conn, state, &request);
    1049              :     }
    1050              : 
    1051            0 :     if (res > 0)
    1052              :     {
    1053              :         PGoauthBearerRequestV2 *request_copy;
    1054              : 
    1055            0 :         if (request.v1.token)
    1056              :         {
    1057              :             /*
    1058              :              * We already have a token, so copy it into the conn. (We can't
    1059              :              * hold onto the original string, since it may not be safe for us
    1060              :              * to free() it.)
    1061              :              */
    1062            0 :             conn->oauth_token = strdup(request.v1.token);
    1063            0 :             if (!conn->oauth_token)
    1064              :             {
    1065            0 :                 libpq_append_conn_error(conn, "out of memory");
    1066            0 :                 goto fail;
    1067              :             }
    1068              : 
    1069              :             /* short-circuit */
    1070            0 :             do_cleanup(state, &request);
    1071            0 :             return true;
    1072              :         }
    1073              : 
    1074            0 :         request_copy = malloc(sizeof(*request_copy));
    1075            0 :         if (!request_copy)
    1076              :         {
    1077            0 :             libpq_append_conn_error(conn, "out of memory");
    1078            0 :             goto fail;
    1079              :         }
    1080              : 
    1081            0 :         *request_copy = request;
    1082              : 
    1083            0 :         conn->async_auth = run_oauth_flow;
    1084            0 :         conn->cleanup_async_auth = cleanup_oauth_flow;
    1085            0 :         state->async_ctx = request_copy;
    1086              : 
    1087            0 :         return true;
    1088              :     }
    1089              : 
    1090              :     /*
    1091              :      * Failure cases: either we tried to set up a flow and failed, or there
    1092              :      * was no flow to try.
    1093              :      */
    1094            0 :     if (res < 0)
    1095            0 :         report_flow_error(conn, &request);
    1096              :     else
    1097            0 :         libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
    1098              : 
    1099            0 : fail:
    1100            0 :     do_cleanup(state, &request);
    1101            0 :     return false;
    1102              : }
    1103              : 
    1104              : /*
    1105              :  * Fill in our issuer identifier (and discovery URI, if possible) using the
    1106              :  * connection parameters. If conn->oauth_discovery_uri can't be populated in
    1107              :  * this function, it will be requested from the server.
    1108              :  */
    1109              : static bool
    1110            0 : setup_oauth_parameters(PGconn *conn)
    1111              : {
    1112              :     /*
    1113              :      * This is the only function that sets conn->oauth_issuer_id. If a
    1114              :      * previous connection attempt has already computed it, don't overwrite it
    1115              :      * or the discovery URI. (There's no reason for them to change once
    1116              :      * they're set, and handle_oauth_sasl_error() will fail the connection if
    1117              :      * the server attempts to switch them on us later.)
    1118              :      */
    1119            0 :     if (conn->oauth_issuer_id)
    1120            0 :         return true;
    1121              : 
    1122              :     /*---
    1123              :      * To talk to a server, we require the user to provide issuer and client
    1124              :      * identifiers.
    1125              :      *
    1126              :      * While it's possible for an OAuth client to support multiple issuers, it
    1127              :      * requires additional effort to make sure the flows in use are safe -- to
    1128              :      * quote RFC 9207,
    1129              :      *
    1130              :      *     OAuth clients that interact with only one authorization server are
    1131              :      *     not vulnerable to mix-up attacks. However, when such clients decide
    1132              :      *     to add support for a second authorization server in the future, they
    1133              :      *     become vulnerable and need to apply countermeasures to mix-up
    1134              :      *     attacks.
    1135              :      *
    1136              :      * For now, we allow only one.
    1137              :      */
    1138            0 :     if (!conn->oauth_issuer || !conn->oauth_client_id)
    1139              :     {
    1140            0 :         libpq_append_conn_error(conn,
    1141              :                                 "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
    1142            0 :         return false;
    1143              :     }
    1144              : 
    1145              :     /*
    1146              :      * oauth_issuer is interpreted differently if it's a well-known discovery
    1147              :      * URI rather than just an issuer identifier.
    1148              :      */
    1149            0 :     if (strstr(conn->oauth_issuer, WK_PREFIX) != NULL)
    1150              :     {
    1151              :         /*
    1152              :          * Convert the URI back to an issuer identifier. (This also performs
    1153              :          * validation of the URI format.)
    1154              :          */
    1155            0 :         conn->oauth_issuer_id = issuer_from_well_known_uri(conn,
    1156            0 :                                                            conn->oauth_issuer);
    1157            0 :         if (!conn->oauth_issuer_id)
    1158            0 :             return false;       /* error message already set */
    1159              : 
    1160            0 :         conn->oauth_discovery_uri = strdup(conn->oauth_issuer);
    1161            0 :         if (!conn->oauth_discovery_uri)
    1162              :         {
    1163            0 :             libpq_append_conn_error(conn, "out of memory");
    1164            0 :             return false;
    1165              :         }
    1166              :     }
    1167              :     else
    1168              :     {
    1169              :         /*
    1170              :          * Treat oauth_issuer as an issuer identifier. We'll ask the server
    1171              :          * for the discovery URI.
    1172              :          */
    1173            0 :         conn->oauth_issuer_id = strdup(conn->oauth_issuer);
    1174            0 :         if (!conn->oauth_issuer_id)
    1175              :         {
    1176            0 :             libpq_append_conn_error(conn, "out of memory");
    1177            0 :             return false;
    1178              :         }
    1179              :     }
    1180              : 
    1181            0 :     return true;
    1182              : }
    1183              : 
    1184              : /*
    1185              :  * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2).
    1186              :  *
    1187              :  * If the necessary OAuth parameters are set up on the connection, this will run
    1188              :  * the client flow asynchronously and present the resulting token to the server.
    1189              :  * Otherwise, an empty discovery response will be sent and any parameters sent
    1190              :  * back by the server will be stored for a second attempt.
    1191              :  *
    1192              :  * For a full description of the API, see libpq/sasl.h.
    1193              :  */
    1194              : static SASLStatus
    1195            0 : oauth_exchange(void *opaq, bool final,
    1196              :                char *input, int inputlen,
    1197              :                char **output, int *outputlen)
    1198              : {
    1199            0 :     fe_oauth_state *state = opaq;
    1200            0 :     PGconn     *conn = state->conn;
    1201            0 :     bool        discover = false;
    1202              : 
    1203            0 :     *output = NULL;
    1204            0 :     *outputlen = 0;
    1205              : 
    1206            0 :     switch (state->step)
    1207              :     {
    1208            0 :         case FE_OAUTH_INIT:
    1209              :             /* We begin in the initial response phase. */
    1210              :             Assert(inputlen == -1);
    1211              : 
    1212            0 :             if (!setup_oauth_parameters(conn))
    1213            0 :                 return SASL_FAILED;
    1214              : 
    1215            0 :             if (conn->oauth_token)
    1216              :             {
    1217              :                 /*
    1218              :                  * A previous connection already fetched the token; we'll use
    1219              :                  * it below.
    1220              :                  */
    1221              :             }
    1222            0 :             else if (conn->oauth_discovery_uri)
    1223              :             {
    1224              :                 /*
    1225              :                  * We don't have a token, but we have a discovery URI already
    1226              :                  * stored. Decide whether we're using a user-provided OAuth
    1227              :                  * flow or the one we have built in.
    1228              :                  */
    1229            0 :                 if (!setup_token_request(conn, state))
    1230            0 :                     return SASL_FAILED;
    1231              : 
    1232            0 :                 if (conn->oauth_token)
    1233              :                 {
    1234              :                     /*
    1235              :                      * A really smart user implementation may have already
    1236              :                      * given us the token (e.g. if there was an unexpired copy
    1237              :                      * already cached), and we can use it immediately.
    1238              :                      */
    1239              :                 }
    1240              :                 else
    1241              :                 {
    1242              :                     /*
    1243              :                      * Otherwise, we'll have to hand the connection over to
    1244              :                      * our OAuth implementation.
    1245              :                      *
    1246              :                      * This could take a while, since it generally involves a
    1247              :                      * user in the loop. To avoid consuming the server's
    1248              :                      * authentication timeout, we'll continue this handshake
    1249              :                      * to the end, so that the server can close its side of
    1250              :                      * the connection. We'll open a second connection later
    1251              :                      * once we've retrieved a token.
    1252              :                      */
    1253            0 :                     discover = true;
    1254              :                 }
    1255              :             }
    1256              :             else
    1257              :             {
    1258              :                 /*
    1259              :                  * If we don't have a token, and we don't have a discovery URI
    1260              :                  * to be able to request a token, we ask the server for one
    1261              :                  * explicitly.
    1262              :                  */
    1263            0 :                 discover = true;
    1264              :             }
    1265              : 
    1266              :             /*
    1267              :              * Generate an initial response. This either contains a token, if
    1268              :              * we have one, or an empty discovery response which is doomed to
    1269              :              * fail.
    1270              :              */
    1271            0 :             *output = client_initial_response(conn, discover);
    1272            0 :             if (!*output)
    1273            0 :                 return SASL_FAILED;
    1274              : 
    1275            0 :             *outputlen = strlen(*output);
    1276            0 :             state->step = FE_OAUTH_BEARER_SENT;
    1277              : 
    1278            0 :             if (conn->oauth_token)
    1279              :             {
    1280              :                 /*
    1281              :                  * For the purposes of require_auth, our side of
    1282              :                  * authentication is done at this point; the server will
    1283              :                  * either accept the connection or send an error. Unlike
    1284              :                  * SCRAM, there is no additional server data to check upon
    1285              :                  * success.
    1286              :                  */
    1287            0 :                 conn->client_finished_auth = true;
    1288              :             }
    1289              : 
    1290            0 :             return SASL_CONTINUE;
    1291              : 
    1292            0 :         case FE_OAUTH_BEARER_SENT:
    1293            0 :             if (final)
    1294              :             {
    1295              :                 /*
    1296              :                  * OAUTHBEARER does not make use of additional data with a
    1297              :                  * successful SASL exchange, so we shouldn't get an
    1298              :                  * AuthenticationSASLFinal message.
    1299              :                  */
    1300            0 :                 libpq_append_conn_error(conn,
    1301              :                                         "server sent unexpected additional OAuth data");
    1302            0 :                 return SASL_FAILED;
    1303              :             }
    1304              : 
    1305              :             /*
    1306              :              * An error message was sent by the server. Respond with the
    1307              :              * required dummy message (RFC 7628, sec. 3.2.3).
    1308              :              */
    1309            0 :             *output = strdup(kvsep);
    1310            0 :             if (unlikely(!*output))
    1311              :             {
    1312            0 :                 libpq_append_conn_error(conn, "out of memory");
    1313            0 :                 return SASL_FAILED;
    1314              :             }
    1315            0 :             *outputlen = strlen(*output);   /* == 1 */
    1316              : 
    1317              :             /* Grab the settings from discovery. */
    1318            0 :             if (!handle_oauth_sasl_error(conn, input, inputlen))
    1319            0 :                 return SASL_FAILED;
    1320              : 
    1321            0 :             if (conn->oauth_token)
    1322              :             {
    1323              :                 /*
    1324              :                  * The server rejected our token. Continue onwards towards the
    1325              :                  * expected FATAL message, but mark our state to catch any
    1326              :                  * unexpected "success" from the server.
    1327              :                  */
    1328            0 :                 state->step = FE_OAUTH_SERVER_ERROR;
    1329            0 :                 return SASL_CONTINUE;
    1330              :             }
    1331              : 
    1332            0 :             if (!conn->async_auth)
    1333              :             {
    1334              :                 /*
    1335              :                  * No OAuth flow is set up yet. Did we get enough information
    1336              :                  * from the server to create one?
    1337              :                  */
    1338            0 :                 if (!conn->oauth_discovery_uri)
    1339              :                 {
    1340            0 :                     libpq_append_conn_error(conn,
    1341              :                                             "server requires OAuth authentication, but no discovery metadata was provided");
    1342            0 :                     return SASL_FAILED;
    1343              :                 }
    1344              : 
    1345              :                 /* Yes. Set up the flow now. */
    1346            0 :                 if (!setup_token_request(conn, state))
    1347            0 :                     return SASL_FAILED;
    1348              : 
    1349            0 :                 if (conn->oauth_token)
    1350              :                 {
    1351              :                     /*
    1352              :                      * A token was available in a custom flow's cache. Skip
    1353              :                      * the asynchronous processing.
    1354              :                      */
    1355            0 :                     goto reconnect;
    1356              :                 }
    1357              :             }
    1358              : 
    1359              :             /*
    1360              :              * Time to retrieve a token. This involves a number of HTTP
    1361              :              * connections and timed waits, so we escape the synchronous auth
    1362              :              * processing and tell PQconnectPoll to transfer control to our
    1363              :              * async implementation.
    1364              :              */
    1365              :             Assert(conn->async_auth);    /* should have been set already */
    1366            0 :             state->step = FE_OAUTH_REQUESTING_TOKEN;
    1367            0 :             return SASL_ASYNC;
    1368              : 
    1369            0 :         case FE_OAUTH_REQUESTING_TOKEN:
    1370              : 
    1371              :             /*
    1372              :              * We've returned successfully from token retrieval. Double-check
    1373              :              * that we have what we need for the next connection.
    1374              :              */
    1375            0 :             if (!conn->oauth_token)
    1376              :             {
    1377              :                 Assert(false);  /* should have failed before this point! */
    1378            0 :                 libpq_append_conn_error(conn,
    1379              :                                         "internal error: OAuth flow did not set a token");
    1380            0 :                 return SASL_FAILED;
    1381              :             }
    1382              : 
    1383            0 :             goto reconnect;
    1384              : 
    1385            0 :         case FE_OAUTH_SERVER_ERROR:
    1386              : 
    1387              :             /*
    1388              :              * After an error, the server should send an error response to
    1389              :              * fail the SASL handshake, which is handled in higher layers.
    1390              :              *
    1391              :              * If we get here, the server either sent *another* challenge
    1392              :              * which isn't defined in the RFC, or completed the handshake
    1393              :              * successfully after telling us it was going to fail. Neither is
    1394              :              * acceptable.
    1395              :              */
    1396            0 :             libpq_append_conn_error(conn,
    1397              :                                     "server sent additional OAuth data after error");
    1398            0 :             return SASL_FAILED;
    1399              : 
    1400            0 :         default:
    1401            0 :             libpq_append_conn_error(conn, "invalid OAuth exchange state");
    1402            0 :             break;
    1403              :     }
    1404              : 
    1405              :     Assert(false);              /* should never get here */
    1406            0 :     return SASL_FAILED;
    1407              : 
    1408            0 : reconnect:
    1409              : 
    1410              :     /*
    1411              :      * Despite being a failure from the point of view of SASL, we have enough
    1412              :      * information to restart with a new connection.
    1413              :      */
    1414            0 :     libpq_append_conn_error(conn, "retrying connection with new bearer token");
    1415            0 :     conn->oauth_want_retry = true;
    1416            0 :     return SASL_FAILED;
    1417              : }
    1418              : 
    1419              : static bool
    1420            0 : oauth_channel_bound(void *opaq)
    1421              : {
    1422              :     /* This mechanism does not support channel binding. */
    1423            0 :     return false;
    1424              : }
    1425              : 
    1426              : /*
    1427              :  * Fully clears out any stored OAuth token. This is done proactively upon
    1428              :  * successful connection as well as during pqClosePGconn().
    1429              :  */
    1430              : void
    1431        30764 : pqClearOAuthToken(PGconn *conn)
    1432              : {
    1433        30764 :     if (!conn->oauth_token)
    1434        30764 :         return;
    1435              : 
    1436            0 :     explicit_bzero(conn->oauth_token, strlen(conn->oauth_token));
    1437            0 :     free(conn->oauth_token);
    1438            0 :     conn->oauth_token = NULL;
    1439              : }
    1440              : 
    1441              : /*
    1442              :  * Hook v1 Poisoning
    1443              :  *
    1444              :  * Try to catch misuses of the v1 PQAUTHDATA_OAUTH_BEARER_TOKEN hook and its
    1445              :  * callbacks, which are not allowed to downcast their request argument to
    1446              :  * PGoauthBearerRequestV2. (Such clients may crash or worse when speaking to
    1447              :  * libpq 18.)
    1448              :  *
    1449              :  * This attempts to use Valgrind hooks, if present, to mark the extra members as
    1450              :  * inaccessible. For uninstrumented builds, it also munges request->issuer to
    1451              :  * try to crash clients that perform string operations, and it aborts if
    1452              :  * request->error is set.
    1453              :  */
    1454              : 
    1455              : #define MASK_BITS ((uintptr_t) 0x55aa55aa55aa55aa)
    1456              : #define POISON_MASK(ptr) ((void *) (((uintptr_t) ptr) ^ MASK_BITS))
    1457              : 
    1458              : /*
    1459              :  * Workhorse for v2 request poisoning. This must be called exactly twice: once
    1460              :  * to poison, once to unpoison.
    1461              :  *
    1462              :  * NB: Unpoisoning must restore the request to its original state, because we
    1463              :  * might still switch back to a v2 implementation internally. Don't do anything
    1464              :  * destructive during the poison operation.
    1465              :  */
    1466              : static void
    1467            0 : poison_req_v2(PGoauthBearerRequestV2 *request, bool poison)
    1468              : {
    1469              : #ifdef USE_VALGRIND
    1470              :     void       *const base = (char *) request + sizeof(request->v1);
    1471              :     const size_t len = sizeof(*request) - sizeof(request->v1);
    1472              : #endif
    1473              : 
    1474            0 :     if (poison)
    1475              :     {
    1476              :         /* Poison request->issuer with a mask to help uninstrumented builds. */
    1477            0 :         request->issuer = POISON_MASK(request->issuer);
    1478              : 
    1479              :         /*
    1480              :          * We'll check to make sure request->error wasn't assigned when
    1481              :          * unpoisoning, so it had better not be assigned now.
    1482              :          */
    1483              :         Assert(!request->error);
    1484              : 
    1485              :         VALGRIND_MAKE_MEM_NOACCESS(base, len);
    1486              :     }
    1487              :     else
    1488              :     {
    1489              :         /*
    1490              :          * XXX Using DEFINED here is technically too lax; we might catch
    1491              :          * struct padding in the blast radius. But since this API has to
    1492              :          * poison stack addresses, and Valgrind can't track/manage undefined
    1493              :          * stack regions, we can't be any stricter without tracking the
    1494              :          * original state of the memory.
    1495              :          */
    1496              :         VALGRIND_MAKE_MEM_DEFINED(base, len);
    1497              : 
    1498              :         /* Undo our mask. */
    1499            0 :         request->issuer = POISON_MASK(request->issuer);
    1500              : 
    1501              :         /*
    1502              :          * For uninstrumented builds, make sure request->error wasn't touched.
    1503              :          */
    1504            0 :         if (request->error)
    1505              :         {
    1506            0 :             fprintf(stderr,
    1507              :                     "abort! out-of-bounds write to PGoauthBearerRequest by PQAUTHDATA_OAUTH_BEARER_TOKEN hook\n");
    1508            0 :             abort();
    1509              :         }
    1510              :     }
    1511            0 : }
    1512              : 
    1513              : /*
    1514              :  * Wrapper around PGoauthBearerRequest.async() which applies poison during the
    1515              :  * callback when necessary.
    1516              :  */
    1517              : static PostgresPollingStatusType
    1518            0 : do_async(fe_oauth_state *state, PGoauthBearerRequestV2 *request)
    1519              : {
    1520              :     PostgresPollingStatusType ret;
    1521            0 :     PGconn     *conn = state->conn;
    1522              : 
    1523              :     Assert(request->v1.async);
    1524              : 
    1525            0 :     if (state->v1)
    1526            0 :         poison_req_v2(request, true);
    1527              : 
    1528            0 :     ret = request->v1.async(conn,
    1529              :                             (PGoauthBearerRequest *) request,
    1530            0 :                             &conn->altsock);
    1531              : 
    1532            0 :     if (state->v1)
    1533            0 :         poison_req_v2(request, false);
    1534              : 
    1535            0 :     return ret;
    1536              : }
    1537              : 
    1538              : /*
    1539              :  * Similar wrapper for the optional PGoauthBearerRequest.cleanup() callback.
    1540              :  * Does nothing if one is not defined.
    1541              :  */
    1542              : static void
    1543            0 : do_cleanup(fe_oauth_state *state, PGoauthBearerRequestV2 *request)
    1544              : {
    1545            0 :     if (!request->v1.cleanup)
    1546            0 :         return;
    1547              : 
    1548            0 :     if (state->v1)
    1549            0 :         poison_req_v2(request, true);
    1550              : 
    1551            0 :     request->v1.cleanup(state->conn, (PGoauthBearerRequest *) request);
    1552              : 
    1553            0 :     if (state->v1)
    1554            0 :         poison_req_v2(request, false);
    1555              : }
        

Generated by: LCOV version 2.0-1