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

Generated by: LCOV version 2.0-1