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

Generated by: LCOV version 2.0-1