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 % 359 3
Test Date: 2026-02-28 12:14:50 Functions: 5.0 % 20 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_user_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              :  * Callback implementation of conn->async_auth() for a user-defined OAuth flow.
     680              :  * Delegates the retrieval of the token to the application's async callback.
     681              :  *
     682              :  * This will be called multiple times as needed; the application is responsible
     683              :  * for setting an altsock to signal and returning the correct PGRES_POLLING_*
     684              :  * statuses for use by PQconnectPoll().
     685              :  */
     686              : static PostgresPollingStatusType
     687            0 : run_user_oauth_flow(PGconn *conn)
     688              : {
     689            0 :     fe_oauth_state *state = conn->sasl_state;
     690            0 :     PGoauthBearerRequest *request = state->async_ctx;
     691              :     PostgresPollingStatusType status;
     692              : 
     693            0 :     if (!request->async)
     694              :     {
     695            0 :         libpq_append_conn_error(conn,
     696              :                                 "user-defined OAuth flow provided neither a token nor an async callback");
     697            0 :         return PGRES_POLLING_FAILED;
     698              :     }
     699              : 
     700            0 :     status = request->async(conn, request, &conn->altsock);
     701            0 :     if (status == PGRES_POLLING_FAILED)
     702              :     {
     703            0 :         libpq_append_conn_error(conn, "user-defined OAuth flow failed");
     704            0 :         return status;
     705              :     }
     706            0 :     else if (status == PGRES_POLLING_OK)
     707              :     {
     708              :         /*
     709              :          * We already have a token, so copy it into the conn. (We can't hold
     710              :          * onto the original string, since it may not be safe for us to free()
     711              :          * it.)
     712              :          */
     713            0 :         if (!request->token)
     714              :         {
     715            0 :             libpq_append_conn_error(conn,
     716              :                                     "user-defined OAuth flow did not provide a token");
     717            0 :             return PGRES_POLLING_FAILED;
     718              :         }
     719              : 
     720            0 :         conn->oauth_token = strdup(request->token);
     721            0 :         if (!conn->oauth_token)
     722              :         {
     723            0 :             libpq_append_conn_error(conn, "out of memory");
     724            0 :             return PGRES_POLLING_FAILED;
     725              :         }
     726              : 
     727            0 :         return PGRES_POLLING_OK;
     728              :     }
     729              : 
     730              :     /* The hook wants the client to poll the altsock. Make sure it set one. */
     731            0 :     if (conn->altsock == PGINVALID_SOCKET)
     732              :     {
     733            0 :         libpq_append_conn_error(conn,
     734              :                                 "user-defined OAuth flow did not provide a socket for polling");
     735            0 :         return PGRES_POLLING_FAILED;
     736              :     }
     737              : 
     738            0 :     return status;
     739              : }
     740              : 
     741              : /*
     742              :  * Cleanup callback for the async user flow. Delegates most of its job to the
     743              :  * user-provided cleanup implementation, then disconnects the altsock.
     744              :  */
     745              : static void
     746            0 : cleanup_user_oauth_flow(PGconn *conn)
     747              : {
     748            0 :     fe_oauth_state *state = conn->sasl_state;
     749            0 :     PGoauthBearerRequest *request = state->async_ctx;
     750              : 
     751              :     Assert(request);
     752              : 
     753            0 :     if (request->cleanup)
     754            0 :         request->cleanup(conn, request);
     755            0 :     conn->altsock = PGINVALID_SOCKET;
     756              : 
     757            0 :     free(request);
     758            0 :     state->async_ctx = NULL;
     759            0 : }
     760              : 
     761              : /*-------------
     762              :  * Builtin Flow
     763              :  *
     764              :  * There are three potential implementations of use_builtin_flow:
     765              :  *
     766              :  * 1) If the OAuth client is disabled at configuration time, return false.
     767              :  *    Dependent clients must provide their own flow.
     768              :  * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
     769              :  *    the libpq-oauth plugin and use its implementation.
     770              :  * 3) Otherwise, use flow callbacks that are statically linked into the
     771              :  *    executable.
     772              :  */
     773              : 
     774              : #if !defined(USE_LIBCURL)
     775              : 
     776              : /*
     777              :  * This configuration doesn't support the builtin flow.
     778              :  */
     779              : 
     780              : bool
     781            0 : use_builtin_flow(PGconn *conn, fe_oauth_state *state)
     782              : {
     783            0 :     return false;
     784              : }
     785              : 
     786              : #elif defined(USE_DYNAMIC_OAUTH)
     787              : 
     788              : /*
     789              :  * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
     790              :  */
     791              : 
     792              : typedef char *(*libpq_gettext_func) (const char *msgid);
     793              : 
     794              : /*
     795              :  * Define accessor/mutator shims to inject into libpq-oauth, so that it doesn't
     796              :  * depend on the offsets within PGconn. (These have changed during minor version
     797              :  * updates in the past.)
     798              :  */
     799              : 
     800              : #define DEFINE_GETTER(TYPE, MEMBER) \
     801              :     typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
     802              :     static TYPE conn_ ## MEMBER(PGconn *conn) { return conn->MEMBER; }
     803              : 
     804              : /* Like DEFINE_GETTER, but returns a pointer to the member. */
     805              : #define DEFINE_GETTER_P(TYPE, MEMBER) \
     806              :     typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
     807              :     static TYPE conn_ ## MEMBER(PGconn *conn) { return &conn->MEMBER; }
     808              : 
     809              : #define DEFINE_SETTER(TYPE, MEMBER) \
     810              :     typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
     811              :     static void set_conn_ ## MEMBER(PGconn *conn, TYPE val) { conn->MEMBER = val; }
     812              : 
     813              : DEFINE_GETTER_P(PQExpBuffer, errorMessage);
     814              : DEFINE_GETTER(char *, oauth_client_id);
     815              : DEFINE_GETTER(char *, oauth_client_secret);
     816              : DEFINE_GETTER(char *, oauth_discovery_uri);
     817              : DEFINE_GETTER(char *, oauth_issuer_id);
     818              : DEFINE_GETTER(char *, oauth_scope);
     819              : DEFINE_GETTER(fe_oauth_state *, sasl_state);
     820              : 
     821              : DEFINE_SETTER(pgsocket, altsock);
     822              : DEFINE_SETTER(char *, oauth_token);
     823              : 
     824              : /*
     825              :  * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
     826              :  * callbacks into the connection's async auth handlers.
     827              :  *
     828              :  * Failure to load here results in a relatively quiet connection error, to
     829              :  * handle the use case where the build supports loading a flow but a user does
     830              :  * not want to install it. Troubleshooting of linker/loader failures can be done
     831              :  * via PGOAUTHDEBUG.
     832              :  */
     833              : bool
     834              : use_builtin_flow(PGconn *conn, fe_oauth_state *state)
     835              : {
     836              :     static bool initialized = false;
     837              :     static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
     838              :     int         lockerr;
     839              : 
     840              :     void        (*init) (pgthreadlock_t threadlock,
     841              :                          libpq_gettext_func gettext_impl,
     842              :                          conn_errorMessage_func errmsg_impl,
     843              :                          conn_oauth_client_id_func clientid_impl,
     844              :                          conn_oauth_client_secret_func clientsecret_impl,
     845              :                          conn_oauth_discovery_uri_func discoveryuri_impl,
     846              :                          conn_oauth_issuer_id_func issuerid_impl,
     847              :                          conn_oauth_scope_func scope_impl,
     848              :                          conn_sasl_state_func saslstate_impl,
     849              :                          set_conn_altsock_func setaltsock_impl,
     850              :                          set_conn_oauth_token_func settoken_impl);
     851              :     PostgresPollingStatusType (*flow) (PGconn *conn);
     852              :     void        (*cleanup) (PGconn *conn);
     853              : 
     854              :     /*
     855              :      * On macOS only, load the module using its absolute install path; the
     856              :      * standard search behavior is not very helpful for this use case. Unlike
     857              :      * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
     858              :      * absolute paths (modulo SIP effects), so tests can continue to work.
     859              :      *
     860              :      * On the other platforms, load the module using only the basename, to
     861              :      * rely on the runtime linker's standard search behavior.
     862              :      */
     863              :     const char *const module_name =
     864              : #if defined(__darwin__)
     865              :         LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
     866              : #else
     867              :         "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
     868              : #endif
     869              : 
     870              :     state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
     871              :     if (!state->builtin_flow)
     872              :     {
     873              :         /*
     874              :          * For end users, this probably isn't an error condition, it just
     875              :          * means the flow isn't installed. Developers and package maintainers
     876              :          * may want to debug this via the PGOAUTHDEBUG envvar, though.
     877              :          *
     878              :          * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
     879              :          */
     880              :         if (oauth_unsafe_debugging_enabled())
     881              :             fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
     882              : 
     883              :         return false;
     884              :     }
     885              : 
     886              :     if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
     887              :         || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
     888              :         || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
     889              :     {
     890              :         /*
     891              :          * This is more of an error condition than the one above, but due to
     892              :          * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
     893              :          */
     894              :         if (oauth_unsafe_debugging_enabled())
     895              :             fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
     896              : 
     897              :         dlclose(state->builtin_flow);
     898              :         return false;
     899              :     }
     900              : 
     901              :     /*
     902              :      * Past this point, we do not unload the module. It stays in the process
     903              :      * permanently.
     904              :      */
     905              : 
     906              :     /*
     907              :      * We need to inject necessary function pointers into the module. This
     908              :      * only needs to be done once -- even if the pointers are constant,
     909              :      * assigning them while another thread is executing the flows feels like
     910              :      * tempting fate.
     911              :      */
     912              :     if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
     913              :     {
     914              :         /* Should not happen... but don't continue if it does. */
     915              :         Assert(false);
     916              : 
     917              :         libpq_append_conn_error(conn, "failed to lock mutex (%d)", lockerr);
     918              :         return false;
     919              :     }
     920              : 
     921              :     if (!initialized)
     922              :     {
     923              :         init(pg_g_threadlock,
     924              : #ifdef ENABLE_NLS
     925              :              libpq_gettext,
     926              : #else
     927              :              NULL,
     928              : #endif
     929              :              conn_errorMessage,
     930              :              conn_oauth_client_id,
     931              :              conn_oauth_client_secret,
     932              :              conn_oauth_discovery_uri,
     933              :              conn_oauth_issuer_id,
     934              :              conn_oauth_scope,
     935              :              conn_sasl_state,
     936              :              set_conn_altsock,
     937              :              set_conn_oauth_token);
     938              : 
     939              :         initialized = true;
     940              :     }
     941              : 
     942              :     pthread_mutex_unlock(&init_mutex);
     943              : 
     944              :     /* Set our asynchronous callbacks. */
     945              :     conn->async_auth = flow;
     946              :     conn->cleanup_async_auth = cleanup;
     947              : 
     948              :     return true;
     949              : }
     950              : 
     951              : #else
     952              : 
     953              : /*
     954              :  * Use the builtin flow in libpq-oauth.a (see libpq-oauth/oauth-curl.h).
     955              :  */
     956              : 
     957              : extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
     958              : extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
     959              : 
     960              : bool
     961              : use_builtin_flow(PGconn *conn, fe_oauth_state *state)
     962              : {
     963              :     /* Set our asynchronous callbacks. */
     964              :     conn->async_auth = pg_fe_run_oauth_flow;
     965              :     conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
     966              : 
     967              :     return true;
     968              : }
     969              : 
     970              : #endif                          /* USE_LIBCURL */
     971              : 
     972              : 
     973              : /*
     974              :  * Chooses an OAuth client flow for the connection, which will retrieve a Bearer
     975              :  * token for presentation to the server.
     976              :  *
     977              :  * If the application has registered a custom flow handler using
     978              :  * PQAUTHDATA_OAUTH_BEARER_TOKEN, it may either return a token immediately (e.g.
     979              :  * if it has one cached for immediate use), or set up for a series of
     980              :  * asynchronous callbacks which will be managed by run_user_oauth_flow().
     981              :  *
     982              :  * If the default handler is used instead, a Device Authorization flow is used
     983              :  * for the connection if support has been compiled in. (See
     984              :  * fe-auth-oauth-curl.c for implementation details.)
     985              :  *
     986              :  * If neither a custom handler nor the builtin flow is available, the connection
     987              :  * fails here.
     988              :  */
     989              : static bool
     990            0 : setup_token_request(PGconn *conn, fe_oauth_state *state)
     991              : {
     992              :     int         res;
     993            0 :     PGoauthBearerRequest request = {
     994            0 :         .openid_configuration = conn->oauth_discovery_uri,
     995            0 :         .scope = conn->oauth_scope,
     996              :     };
     997              : 
     998              :     Assert(request.openid_configuration);
     999              : 
    1000              :     /* The client may have overridden the OAuth flow. */
    1001            0 :     res = PQauthDataHook(PQAUTHDATA_OAUTH_BEARER_TOKEN, conn, &request);
    1002            0 :     if (res > 0)
    1003              :     {
    1004              :         PGoauthBearerRequest *request_copy;
    1005              : 
    1006            0 :         if (request.token)
    1007              :         {
    1008              :             /*
    1009              :              * We already have a token, so copy it into the conn. (We can't
    1010              :              * hold onto the original string, since it may not be safe for us
    1011              :              * to free() it.)
    1012              :              */
    1013            0 :             conn->oauth_token = strdup(request.token);
    1014            0 :             if (!conn->oauth_token)
    1015              :             {
    1016            0 :                 libpq_append_conn_error(conn, "out of memory");
    1017            0 :                 goto fail;
    1018              :             }
    1019              : 
    1020              :             /* short-circuit */
    1021            0 :             if (request.cleanup)
    1022            0 :                 request.cleanup(conn, &request);
    1023            0 :             return true;
    1024              :         }
    1025              : 
    1026            0 :         request_copy = malloc(sizeof(*request_copy));
    1027            0 :         if (!request_copy)
    1028              :         {
    1029            0 :             libpq_append_conn_error(conn, "out of memory");
    1030            0 :             goto fail;
    1031              :         }
    1032              : 
    1033            0 :         *request_copy = request;
    1034              : 
    1035            0 :         conn->async_auth = run_user_oauth_flow;
    1036            0 :         conn->cleanup_async_auth = cleanup_user_oauth_flow;
    1037            0 :         state->async_ctx = request_copy;
    1038              :     }
    1039            0 :     else if (res < 0)
    1040              :     {
    1041            0 :         libpq_append_conn_error(conn, "user-defined OAuth flow failed");
    1042            0 :         goto fail;
    1043              :     }
    1044            0 :     else if (!use_builtin_flow(conn, state))
    1045              :     {
    1046            0 :         libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
    1047            0 :         goto fail;
    1048              :     }
    1049              : 
    1050            0 :     return true;
    1051              : 
    1052            0 : fail:
    1053            0 :     if (request.cleanup)
    1054            0 :         request.cleanup(conn, &request);
    1055            0 :     return false;
    1056              : }
    1057              : 
    1058              : /*
    1059              :  * Fill in our issuer identifier (and discovery URI, if possible) using the
    1060              :  * connection parameters. If conn->oauth_discovery_uri can't be populated in
    1061              :  * this function, it will be requested from the server.
    1062              :  */
    1063              : static bool
    1064            0 : setup_oauth_parameters(PGconn *conn)
    1065              : {
    1066              :     /*
    1067              :      * This is the only function that sets conn->oauth_issuer_id. If a
    1068              :      * previous connection attempt has already computed it, don't overwrite it
    1069              :      * or the discovery URI. (There's no reason for them to change once
    1070              :      * they're set, and handle_oauth_sasl_error() will fail the connection if
    1071              :      * the server attempts to switch them on us later.)
    1072              :      */
    1073            0 :     if (conn->oauth_issuer_id)
    1074            0 :         return true;
    1075              : 
    1076              :     /*---
    1077              :      * To talk to a server, we require the user to provide issuer and client
    1078              :      * identifiers.
    1079              :      *
    1080              :      * While it's possible for an OAuth client to support multiple issuers, it
    1081              :      * requires additional effort to make sure the flows in use are safe -- to
    1082              :      * quote RFC 9207,
    1083              :      *
    1084              :      *     OAuth clients that interact with only one authorization server are
    1085              :      *     not vulnerable to mix-up attacks. However, when such clients decide
    1086              :      *     to add support for a second authorization server in the future, they
    1087              :      *     become vulnerable and need to apply countermeasures to mix-up
    1088              :      *     attacks.
    1089              :      *
    1090              :      * For now, we allow only one.
    1091              :      */
    1092            0 :     if (!conn->oauth_issuer || !conn->oauth_client_id)
    1093              :     {
    1094            0 :         libpq_append_conn_error(conn,
    1095              :                                 "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
    1096            0 :         return false;
    1097              :     }
    1098              : 
    1099              :     /*
    1100              :      * oauth_issuer is interpreted differently if it's a well-known discovery
    1101              :      * URI rather than just an issuer identifier.
    1102              :      */
    1103            0 :     if (strstr(conn->oauth_issuer, WK_PREFIX) != NULL)
    1104              :     {
    1105              :         /*
    1106              :          * Convert the URI back to an issuer identifier. (This also performs
    1107              :          * validation of the URI format.)
    1108              :          */
    1109            0 :         conn->oauth_issuer_id = issuer_from_well_known_uri(conn,
    1110            0 :                                                            conn->oauth_issuer);
    1111            0 :         if (!conn->oauth_issuer_id)
    1112            0 :             return false;       /* error message already set */
    1113              : 
    1114            0 :         conn->oauth_discovery_uri = strdup(conn->oauth_issuer);
    1115            0 :         if (!conn->oauth_discovery_uri)
    1116              :         {
    1117            0 :             libpq_append_conn_error(conn, "out of memory");
    1118            0 :             return false;
    1119              :         }
    1120              :     }
    1121              :     else
    1122              :     {
    1123              :         /*
    1124              :          * Treat oauth_issuer as an issuer identifier. We'll ask the server
    1125              :          * for the discovery URI.
    1126              :          */
    1127            0 :         conn->oauth_issuer_id = strdup(conn->oauth_issuer);
    1128            0 :         if (!conn->oauth_issuer_id)
    1129              :         {
    1130            0 :             libpq_append_conn_error(conn, "out of memory");
    1131            0 :             return false;
    1132              :         }
    1133              :     }
    1134              : 
    1135            0 :     return true;
    1136              : }
    1137              : 
    1138              : /*
    1139              :  * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2).
    1140              :  *
    1141              :  * If the necessary OAuth parameters are set up on the connection, this will run
    1142              :  * the client flow asynchronously and present the resulting token to the server.
    1143              :  * Otherwise, an empty discovery response will be sent and any parameters sent
    1144              :  * back by the server will be stored for a second attempt.
    1145              :  *
    1146              :  * For a full description of the API, see libpq/sasl.h.
    1147              :  */
    1148              : static SASLStatus
    1149            0 : oauth_exchange(void *opaq, bool final,
    1150              :                char *input, int inputlen,
    1151              :                char **output, int *outputlen)
    1152              : {
    1153            0 :     fe_oauth_state *state = opaq;
    1154            0 :     PGconn     *conn = state->conn;
    1155            0 :     bool        discover = false;
    1156              : 
    1157            0 :     *output = NULL;
    1158            0 :     *outputlen = 0;
    1159              : 
    1160            0 :     switch (state->step)
    1161              :     {
    1162            0 :         case FE_OAUTH_INIT:
    1163              :             /* We begin in the initial response phase. */
    1164              :             Assert(inputlen == -1);
    1165              : 
    1166            0 :             if (!setup_oauth_parameters(conn))
    1167            0 :                 return SASL_FAILED;
    1168              : 
    1169            0 :             if (conn->oauth_token)
    1170              :             {
    1171              :                 /*
    1172              :                  * A previous connection already fetched the token; we'll use
    1173              :                  * it below.
    1174              :                  */
    1175              :             }
    1176            0 :             else if (conn->oauth_discovery_uri)
    1177              :             {
    1178              :                 /*
    1179              :                  * We don't have a token, but we have a discovery URI already
    1180              :                  * stored. Decide whether we're using a user-provided OAuth
    1181              :                  * flow or the one we have built in.
    1182              :                  */
    1183            0 :                 if (!setup_token_request(conn, state))
    1184            0 :                     return SASL_FAILED;
    1185              : 
    1186            0 :                 if (conn->oauth_token)
    1187              :                 {
    1188              :                     /*
    1189              :                      * A really smart user implementation may have already
    1190              :                      * given us the token (e.g. if there was an unexpired copy
    1191              :                      * already cached), and we can use it immediately.
    1192              :                      */
    1193              :                 }
    1194              :                 else
    1195              :                 {
    1196              :                     /*
    1197              :                      * Otherwise, we'll have to hand the connection over to
    1198              :                      * our OAuth implementation.
    1199              :                      *
    1200              :                      * This could take a while, since it generally involves a
    1201              :                      * user in the loop. To avoid consuming the server's
    1202              :                      * authentication timeout, we'll continue this handshake
    1203              :                      * to the end, so that the server can close its side of
    1204              :                      * the connection. We'll open a second connection later
    1205              :                      * once we've retrieved a token.
    1206              :                      */
    1207            0 :                     discover = true;
    1208              :                 }
    1209              :             }
    1210              :             else
    1211              :             {
    1212              :                 /*
    1213              :                  * If we don't have a token, and we don't have a discovery URI
    1214              :                  * to be able to request a token, we ask the server for one
    1215              :                  * explicitly.
    1216              :                  */
    1217            0 :                 discover = true;
    1218              :             }
    1219              : 
    1220              :             /*
    1221              :              * Generate an initial response. This either contains a token, if
    1222              :              * we have one, or an empty discovery response which is doomed to
    1223              :              * fail.
    1224              :              */
    1225            0 :             *output = client_initial_response(conn, discover);
    1226            0 :             if (!*output)
    1227            0 :                 return SASL_FAILED;
    1228              : 
    1229            0 :             *outputlen = strlen(*output);
    1230            0 :             state->step = FE_OAUTH_BEARER_SENT;
    1231              : 
    1232            0 :             if (conn->oauth_token)
    1233              :             {
    1234              :                 /*
    1235              :                  * For the purposes of require_auth, our side of
    1236              :                  * authentication is done at this point; the server will
    1237              :                  * either accept the connection or send an error. Unlike
    1238              :                  * SCRAM, there is no additional server data to check upon
    1239              :                  * success.
    1240              :                  */
    1241            0 :                 conn->client_finished_auth = true;
    1242              :             }
    1243              : 
    1244            0 :             return SASL_CONTINUE;
    1245              : 
    1246            0 :         case FE_OAUTH_BEARER_SENT:
    1247            0 :             if (final)
    1248              :             {
    1249              :                 /*
    1250              :                  * OAUTHBEARER does not make use of additional data with a
    1251              :                  * successful SASL exchange, so we shouldn't get an
    1252              :                  * AuthenticationSASLFinal message.
    1253              :                  */
    1254            0 :                 libpq_append_conn_error(conn,
    1255              :                                         "server sent unexpected additional OAuth data");
    1256            0 :                 return SASL_FAILED;
    1257              :             }
    1258              : 
    1259              :             /*
    1260              :              * An error message was sent by the server. Respond with the
    1261              :              * required dummy message (RFC 7628, sec. 3.2.3).
    1262              :              */
    1263            0 :             *output = strdup(kvsep);
    1264            0 :             if (unlikely(!*output))
    1265              :             {
    1266            0 :                 libpq_append_conn_error(conn, "out of memory");
    1267            0 :                 return SASL_FAILED;
    1268              :             }
    1269            0 :             *outputlen = strlen(*output);   /* == 1 */
    1270              : 
    1271              :             /* Grab the settings from discovery. */
    1272            0 :             if (!handle_oauth_sasl_error(conn, input, inputlen))
    1273            0 :                 return SASL_FAILED;
    1274              : 
    1275            0 :             if (conn->oauth_token)
    1276              :             {
    1277              :                 /*
    1278              :                  * The server rejected our token. Continue onwards towards the
    1279              :                  * expected FATAL message, but mark our state to catch any
    1280              :                  * unexpected "success" from the server.
    1281              :                  */
    1282            0 :                 state->step = FE_OAUTH_SERVER_ERROR;
    1283            0 :                 return SASL_CONTINUE;
    1284              :             }
    1285              : 
    1286            0 :             if (!conn->async_auth)
    1287              :             {
    1288              :                 /*
    1289              :                  * No OAuth flow is set up yet. Did we get enough information
    1290              :                  * from the server to create one?
    1291              :                  */
    1292            0 :                 if (!conn->oauth_discovery_uri)
    1293              :                 {
    1294            0 :                     libpq_append_conn_error(conn,
    1295              :                                             "server requires OAuth authentication, but no discovery metadata was provided");
    1296            0 :                     return SASL_FAILED;
    1297              :                 }
    1298              : 
    1299              :                 /* Yes. Set up the flow now. */
    1300            0 :                 if (!setup_token_request(conn, state))
    1301            0 :                     return SASL_FAILED;
    1302              : 
    1303            0 :                 if (conn->oauth_token)
    1304              :                 {
    1305              :                     /*
    1306              :                      * A token was available in a custom flow's cache. Skip
    1307              :                      * the asynchronous processing.
    1308              :                      */
    1309            0 :                     goto reconnect;
    1310              :                 }
    1311              :             }
    1312              : 
    1313              :             /*
    1314              :              * Time to retrieve a token. This involves a number of HTTP
    1315              :              * connections and timed waits, so we escape the synchronous auth
    1316              :              * processing and tell PQconnectPoll to transfer control to our
    1317              :              * async implementation.
    1318              :              */
    1319              :             Assert(conn->async_auth);    /* should have been set already */
    1320            0 :             state->step = FE_OAUTH_REQUESTING_TOKEN;
    1321            0 :             return SASL_ASYNC;
    1322              : 
    1323            0 :         case FE_OAUTH_REQUESTING_TOKEN:
    1324              : 
    1325              :             /*
    1326              :              * We've returned successfully from token retrieval. Double-check
    1327              :              * that we have what we need for the next connection.
    1328              :              */
    1329            0 :             if (!conn->oauth_token)
    1330              :             {
    1331              :                 Assert(false);  /* should have failed before this point! */
    1332            0 :                 libpq_append_conn_error(conn,
    1333              :                                         "internal error: OAuth flow did not set a token");
    1334            0 :                 return SASL_FAILED;
    1335              :             }
    1336              : 
    1337            0 :             goto reconnect;
    1338              : 
    1339            0 :         case FE_OAUTH_SERVER_ERROR:
    1340              : 
    1341              :             /*
    1342              :              * After an error, the server should send an error response to
    1343              :              * fail the SASL handshake, which is handled in higher layers.
    1344              :              *
    1345              :              * If we get here, the server either sent *another* challenge
    1346              :              * which isn't defined in the RFC, or completed the handshake
    1347              :              * successfully after telling us it was going to fail. Neither is
    1348              :              * acceptable.
    1349              :              */
    1350            0 :             libpq_append_conn_error(conn,
    1351              :                                     "server sent additional OAuth data after error");
    1352            0 :             return SASL_FAILED;
    1353              : 
    1354            0 :         default:
    1355            0 :             libpq_append_conn_error(conn, "invalid OAuth exchange state");
    1356            0 :             break;
    1357              :     }
    1358              : 
    1359              :     Assert(false);              /* should never get here */
    1360            0 :     return SASL_FAILED;
    1361              : 
    1362            0 : reconnect:
    1363              : 
    1364              :     /*
    1365              :      * Despite being a failure from the point of view of SASL, we have enough
    1366              :      * information to restart with a new connection.
    1367              :      */
    1368            0 :     libpq_append_conn_error(conn, "retrying connection with new bearer token");
    1369            0 :     conn->oauth_want_retry = true;
    1370            0 :     return SASL_FAILED;
    1371              : }
    1372              : 
    1373              : static bool
    1374            0 : oauth_channel_bound(void *opaq)
    1375              : {
    1376              :     /* This mechanism does not support channel binding. */
    1377            0 :     return false;
    1378              : }
    1379              : 
    1380              : /*
    1381              :  * Fully clears out any stored OAuth token. This is done proactively upon
    1382              :  * successful connection as well as during pqClosePGconn().
    1383              :  */
    1384              : void
    1385        29218 : pqClearOAuthToken(PGconn *conn)
    1386              : {
    1387        29218 :     if (!conn->oauth_token)
    1388        29218 :         return;
    1389              : 
    1390            0 :     explicit_bzero(conn->oauth_token, strlen(conn->oauth_token));
    1391            0 :     free(conn->oauth_token);
    1392            0 :     conn->oauth_token = NULL;
    1393              : }
    1394              : 
    1395              : /*
    1396              :  * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
    1397              :  */
    1398              : bool
    1399            0 : oauth_unsafe_debugging_enabled(void)
    1400              : {
    1401            0 :     const char *env = getenv("PGOAUTHDEBUG");
    1402              : 
    1403            0 :     return (env && strcmp(env, "UNSAFE") == 0);
    1404              : }
        

Generated by: LCOV version 2.0-1