LCOV - code coverage report
Current view: top level - src/interfaces/libpq-oauth - oauth-curl.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 957 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 52 0
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * oauth-curl.c
       4              :  *         The libcurl implementation of OAuth/OIDC authentication, using the
       5              :  *         OAuth Device Authorization Grant (RFC 8628).
       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-oauth/oauth-curl.c
      12              :  *
      13              :  *-------------------------------------------------------------------------
      14              :  */
      15              : 
      16              : #include "postgres_fe.h"
      17              : 
      18              : #include <curl/curl.h>
      19              : #include <math.h>
      20              : #include <unistd.h>
      21              : 
      22              : #if defined(HAVE_SYS_EPOLL_H)
      23              : #include <sys/epoll.h>
      24              : #include <sys/timerfd.h>
      25              : #elif defined(HAVE_SYS_EVENT_H)
      26              : #include <sys/event.h>
      27              : #else
      28              : #error libpq-oauth is not supported on this platform
      29              : #endif
      30              : 
      31              : #include "common/jsonapi.h"
      32              : #include "fe-auth-oauth.h"
      33              : #include "mb/pg_wchar.h"
      34              : #include "oauth-curl.h"
      35              : 
      36              : #ifdef USE_DYNAMIC_OAUTH
      37              : 
      38              : /*
      39              :  * The module build is decoupled from libpq-int.h, to try to avoid inadvertent
      40              :  * ABI breaks during minor version bumps. Replacements for the missing internals
      41              :  * are provided by oauth-utils.
      42              :  */
      43              : #include "oauth-utils.h"
      44              : 
      45              : #else                                                   /* !USE_DYNAMIC_OAUTH */
      46              : 
      47              : /*
      48              :  * Static builds may rely on PGconn offsets directly. Keep these aligned with
      49              :  * the bank of callbacks in oauth-utils.h.
      50              :  */
      51              : #include "libpq-int.h"
      52              : 
      53              : #define conn_errorMessage(CONN) (&CONN->errorMessage)
      54              : #define conn_oauth_client_id(CONN) (CONN->oauth_client_id)
      55              : #define conn_oauth_client_secret(CONN) (CONN->oauth_client_secret)
      56              : #define conn_oauth_discovery_uri(CONN) (CONN->oauth_discovery_uri)
      57              : #define conn_oauth_issuer_id(CONN) (CONN->oauth_issuer_id)
      58              : #define conn_oauth_scope(CONN) (CONN->oauth_scope)
      59              : #define conn_sasl_state(CONN) (CONN->sasl_state)
      60              : 
      61              : #define set_conn_altsock(CONN, VAL) do { CONN->altsock = VAL; } while (0)
      62              : #define set_conn_oauth_token(CONN, VAL) do { CONN->oauth_token = VAL; } while (0)
      63              : 
      64              : #endif                                                  /* USE_DYNAMIC_OAUTH */
      65              : 
      66              : /* One final guardrail against accidental inclusion... */
      67              : #if defined(USE_DYNAMIC_OAUTH) && defined(LIBPQ_INT_H)
      68              : #error do not rely on libpq-int.h in dynamic builds of libpq-oauth
      69              : #endif
      70              : 
      71              : /*
      72              :  * It's generally prudent to set a maximum response size to buffer in memory,
      73              :  * but it's less clear what size to choose. The biggest of our expected
      74              :  * responses is the server metadata JSON, which will only continue to grow in
      75              :  * size; the number of IANA-registered parameters in that document is up to 78
      76              :  * as of February 2025.
      77              :  *
      78              :  * Even if every single parameter were to take up 2k on average (a previously
      79              :  * common limit on the size of a URL), 256k gives us 128 parameter values before
      80              :  * we give up. (That's almost certainly complete overkill in practice; 2-4k
      81              :  * appears to be common among popular providers at the moment.)
      82              :  */
      83              : #define MAX_OAUTH_RESPONSE_SIZE (256 * 1024)
      84              : 
      85              : /*
      86              :  * Similarly, a limit on the maximum JSON nesting level keeps a server from
      87              :  * running us out of stack space. A common nesting level in practice is 2 (for a
      88              :  * top-level object containing arrays of strings). As of May 2025, the maximum
      89              :  * depth for standard server metadata appears to be 6, if the document contains
      90              :  * a full JSON Web Key Set in its "jwks" parameter.
      91              :  *
      92              :  * Since it's easy to nest JSON, and the number of parameters and key types
      93              :  * keeps growing, take a healthy buffer of 16. (If this ever proves to be a
      94              :  * problem in practice, we may want to switch over to the incremental JSON
      95              :  * parser instead of playing with this parameter.)
      96              :  */
      97              : #define MAX_OAUTH_NESTING_LEVEL 16
      98              : 
      99              : /*
     100              :  * Parsed JSON Representations
     101              :  *
     102              :  * As a general rule, we parse and cache only the fields we're currently using.
     103              :  * When adding new fields, ensure the corresponding free_*() function is updated
     104              :  * too.
     105              :  */
     106              : 
     107              : /*
     108              :  * The OpenID Provider configuration (alternatively named "authorization server
     109              :  * metadata") jointly described by OpenID Connect Discovery 1.0 and RFC 8414:
     110              :  *
     111              :  *     https://openid.net/specs/openid-connect-discovery-1_0.html
     112              :  *     https://www.rfc-editor.org/rfc/rfc8414#section-3.2
     113              :  */
     114              : struct provider
     115              : {
     116              :         char       *issuer;
     117              :         char       *token_endpoint;
     118              :         char       *device_authorization_endpoint;
     119              :         struct curl_slist *grant_types_supported;
     120              : };
     121              : 
     122              : static void
     123            0 : free_provider(struct provider *provider)
     124              : {
     125            0 :         free(provider->issuer);
     126            0 :         free(provider->token_endpoint);
     127            0 :         free(provider->device_authorization_endpoint);
     128            0 :         curl_slist_free_all(provider->grant_types_supported);
     129            0 : }
     130              : 
     131              : /*
     132              :  * The Device Authorization response, described by RFC 8628:
     133              :  *
     134              :  *     https://www.rfc-editor.org/rfc/rfc8628#section-3.2
     135              :  */
     136              : struct device_authz
     137              : {
     138              :         char       *device_code;
     139              :         char       *user_code;
     140              :         char       *verification_uri;
     141              :         char       *verification_uri_complete;
     142              :         char       *expires_in_str;
     143              :         char       *interval_str;
     144              : 
     145              :         /* Fields below are parsed from the corresponding string above. */
     146              :         int                     expires_in;
     147              :         int                     interval;
     148              : };
     149              : 
     150              : static void
     151            0 : free_device_authz(struct device_authz *authz)
     152              : {
     153            0 :         free(authz->device_code);
     154            0 :         free(authz->user_code);
     155            0 :         free(authz->verification_uri);
     156            0 :         free(authz->verification_uri_complete);
     157            0 :         free(authz->expires_in_str);
     158            0 :         free(authz->interval_str);
     159            0 : }
     160              : 
     161              : /*
     162              :  * The Token Endpoint error response, as described by RFC 6749:
     163              :  *
     164              :  *     https://www.rfc-editor.org/rfc/rfc6749#section-5.2
     165              :  *
     166              :  * Note that this response type can also be returned from the Device
     167              :  * Authorization Endpoint.
     168              :  */
     169              : struct token_error
     170              : {
     171              :         char       *error;
     172              :         char       *error_description;
     173              : };
     174              : 
     175              : static void
     176            0 : free_token_error(struct token_error *err)
     177              : {
     178            0 :         free(err->error);
     179            0 :         free(err->error_description);
     180            0 : }
     181              : 
     182              : /*
     183              :  * The Access Token response, as described by RFC 6749:
     184              :  *
     185              :  *     https://www.rfc-editor.org/rfc/rfc6749#section-4.1.4
     186              :  *
     187              :  * During the Device Authorization flow, several temporary errors are expected
     188              :  * as part of normal operation. To make it easy to handle these in the happy
     189              :  * path, this contains an embedded token_error that is filled in if needed.
     190              :  */
     191              : struct token
     192              : {
     193              :         /* for successful responses */
     194              :         char       *access_token;
     195              :         char       *token_type;
     196              : 
     197              :         /* for error responses */
     198              :         struct token_error err;
     199              : };
     200              : 
     201              : static void
     202            0 : free_token(struct token *tok)
     203              : {
     204            0 :         free(tok->access_token);
     205            0 :         free(tok->token_type);
     206            0 :         free_token_error(&tok->err);
     207            0 : }
     208              : 
     209              : /*
     210              :  * Asynchronous State
     211              :  */
     212              : 
     213              : /* States for the overall async machine. */
     214              : enum OAuthStep
     215              : {
     216              :         OAUTH_STEP_INIT = 0,
     217              :         OAUTH_STEP_DISCOVERY,
     218              :         OAUTH_STEP_DEVICE_AUTHORIZATION,
     219              :         OAUTH_STEP_TOKEN_REQUEST,
     220              :         OAUTH_STEP_WAIT_INTERVAL,
     221              : };
     222              : 
     223              : /*
     224              :  * The async_ctx holds onto state that needs to persist across multiple calls
     225              :  * to pg_fe_run_oauth_flow(). Almost everything interacts with this in some
     226              :  * way.
     227              :  */
     228              : struct async_ctx
     229              : {
     230              :         enum OAuthStep step;            /* where are we in the flow? */
     231              : 
     232              :         int                     timerfd;                /* descriptor for signaling async timeouts */
     233              :         pgsocket        mux;                    /* the multiplexer socket containing all
     234              :                                                                  * descriptors tracked by libcurl, plus the
     235              :                                                                  * timerfd */
     236              :         CURLM      *curlm;                      /* top-level multi handle for libcurl
     237              :                                                                  * operations */
     238              :         CURL       *curl;                       /* the (single) easy handle for serial
     239              :                                                                  * requests */
     240              : 
     241              :         struct curl_slist *headers; /* common headers for all requests */
     242              :         PQExpBufferData work_data;      /* scratch buffer for general use (remember to
     243              :                                                                  * clear out prior contents first!) */
     244              : 
     245              :         /*------
     246              :          * Since a single logical operation may stretch across multiple calls to
     247              :          * our entry point, errors have three parts:
     248              :          *
     249              :          * - errctx:    an optional static string, describing the global operation
     250              :          *                              currently in progress. Should be translated with
     251              :          *                              libpq_gettext().
     252              :          *
     253              :          * - errbuf:    contains the actual error message. Generally speaking, use
     254              :          *                              actx_error[_str] to manipulate this. This must be filled
     255              :          *                              with something useful on an error.
     256              :          *
     257              :          * - curl_err:  an optional static error buffer used by libcurl to put
     258              :          *                              detailed information about failures. Unfortunately
     259              :          *                              untranslatable.
     260              :          *
     261              :          * These pieces will be combined into a single error message looking
     262              :          * something like the following, with errctx and/or curl_err omitted when
     263              :          * absent:
     264              :          *
     265              :          *     connection to server ... failed: errctx: errbuf (libcurl: curl_err)
     266              :          */
     267              :         const char *errctx;                     /* not freed; must point to static allocation */
     268              :         PQExpBufferData errbuf;
     269              :         char            curl_err[CURL_ERROR_SIZE];
     270              : 
     271              :         /*
     272              :          * These documents need to survive over multiple calls, and are therefore
     273              :          * cached directly in the async_ctx.
     274              :          */
     275              :         struct provider provider;
     276              :         struct device_authz authz;
     277              : 
     278              :         int                     running;                /* is asynchronous work in progress? */
     279              :         bool            user_prompted;  /* have we already sent the authz prompt? */
     280              :         bool            used_basic_auth;        /* did we send a client secret? */
     281              :         bool            debugging;              /* can we give unsafe developer assistance? */
     282              :         int                     dbg_num_calls;  /* (debug mode) how many times were we called? */
     283              : };
     284              : 
     285              : /*
     286              :  * Tears down the Curl handles and frees the async_ctx.
     287              :  */
     288              : static void
     289            0 : free_async_ctx(PGconn *conn, struct async_ctx *actx)
     290              : {
     291              :         /*
     292              :          * In general, none of the error cases below should ever happen if we have
     293              :          * no bugs above. But if we do hit them, surfacing those errors somehow
     294              :          * might be the only way to have a chance to debug them.
     295              :          *
     296              :          * TODO: At some point it'd be nice to have a standard way to warn about
     297              :          * teardown failures. Appending to the connection's error message only
     298              :          * helps if the bug caused a connection failure; otherwise it'll be
     299              :          * buried...
     300              :          */
     301              : 
     302            0 :         if (actx->curlm && actx->curl)
     303              :         {
     304            0 :                 CURLMcode       err = curl_multi_remove_handle(actx->curlm, actx->curl);
     305              : 
     306            0 :                 if (err)
     307            0 :                         libpq_append_conn_error(conn,
     308              :                                                                         "libcurl easy handle removal failed: %s",
     309            0 :                                                                         curl_multi_strerror(err));
     310            0 :         }
     311              : 
     312            0 :         if (actx->curl)
     313              :         {
     314              :                 /*
     315              :                  * curl_multi_cleanup() doesn't free any associated easy handles; we
     316              :                  * need to do that separately. We only ever have one easy handle per
     317              :                  * multi handle.
     318              :                  */
     319            0 :                 curl_easy_cleanup(actx->curl);
     320            0 :         }
     321              : 
     322            0 :         if (actx->curlm)
     323              :         {
     324            0 :                 CURLMcode       err = curl_multi_cleanup(actx->curlm);
     325              : 
     326            0 :                 if (err)
     327            0 :                         libpq_append_conn_error(conn,
     328              :                                                                         "libcurl multi handle cleanup failed: %s",
     329            0 :                                                                         curl_multi_strerror(err));
     330            0 :         }
     331              : 
     332            0 :         free_provider(&actx->provider);
     333            0 :         free_device_authz(&actx->authz);
     334              : 
     335            0 :         curl_slist_free_all(actx->headers);
     336            0 :         termPQExpBuffer(&actx->work_data);
     337            0 :         termPQExpBuffer(&actx->errbuf);
     338              : 
     339            0 :         if (actx->mux != PGINVALID_SOCKET)
     340            0 :                 close(actx->mux);
     341            0 :         if (actx->timerfd >= 0)
     342            0 :                 close(actx->timerfd);
     343              : 
     344            0 :         free(actx);
     345            0 : }
     346              : 
     347              : /*
     348              :  * Release resources used for the asynchronous exchange and disconnect the
     349              :  * altsock.
     350              :  *
     351              :  * This is called either at the end of a successful authentication, or during
     352              :  * pqDropConnection(), so we won't leak resources even if PQconnectPoll() never
     353              :  * calls us back.
     354              :  */
     355              : void
     356            0 : pg_fe_cleanup_oauth_flow(PGconn *conn)
     357              : {
     358            0 :         fe_oauth_state *state = conn_sasl_state(conn);
     359              : 
     360            0 :         if (state->async_ctx)
     361              :         {
     362            0 :                 free_async_ctx(conn, state->async_ctx);
     363            0 :                 state->async_ctx = NULL;
     364            0 :         }
     365              : 
     366            0 :         set_conn_altsock(conn, PGINVALID_SOCKET);
     367            0 : }
     368              : 
     369              : /*
     370              :  * Macros for manipulating actx->errbuf. actx_error() translates and formats a
     371              :  * string for you, actx_error_internal() is the untranslated equivalent, and
     372              :  * actx_error_str() appends a string directly (also without translation).
     373              :  */
     374              : 
     375              : #define actx_error(ACTX, FMT, ...) \
     376              :         appendPQExpBuffer(&(ACTX)->errbuf, libpq_gettext(FMT), ##__VA_ARGS__)
     377              : 
     378              : #define actx_error_internal(ACTX, FMT, ...) \
     379              :         appendPQExpBuffer(&(ACTX)->errbuf, FMT, ##__VA_ARGS__)
     380              : 
     381              : #define actx_error_str(ACTX, S) \
     382              :         appendPQExpBufferStr(&(ACTX)->errbuf, S)
     383              : 
     384              : /*
     385              :  * Macros for getting and setting state for the connection's two libcurl
     386              :  * handles, so you don't have to write out the error handling every time.
     387              :  */
     388              : 
     389              : #define CHECK_MSETOPT(ACTX, OPT, VAL, FAILACTION) \
     390              :         do { \
     391              :                 struct async_ctx *_actx = (ACTX); \
     392              :                 CURLMcode       _setopterr = curl_multi_setopt(_actx->curlm, OPT, VAL); \
     393              :                 if (_setopterr) { \
     394              :                         actx_error(_actx, "failed to set %s on OAuth connection: %s",\
     395              :                                            #OPT, curl_multi_strerror(_setopterr)); \
     396              :                         FAILACTION; \
     397              :                 } \
     398              :         } while (0)
     399              : 
     400              : #define CHECK_SETOPT(ACTX, OPT, VAL, FAILACTION) \
     401              :         do { \
     402              :                 struct async_ctx *_actx = (ACTX); \
     403              :                 CURLcode        _setopterr = curl_easy_setopt(_actx->curl, OPT, VAL); \
     404              :                 if (_setopterr) { \
     405              :                         actx_error(_actx, "failed to set %s on OAuth connection: %s",\
     406              :                                            #OPT, curl_easy_strerror(_setopterr)); \
     407              :                         FAILACTION; \
     408              :                 } \
     409              :         } while (0)
     410              : 
     411              : #define CHECK_GETINFO(ACTX, INFO, OUT, FAILACTION) \
     412              :         do { \
     413              :                 struct async_ctx *_actx = (ACTX); \
     414              :                 CURLcode        _getinfoerr = curl_easy_getinfo(_actx->curl, INFO, OUT); \
     415              :                 if (_getinfoerr) { \
     416              :                         actx_error(_actx, "failed to get %s from OAuth response: %s",\
     417              :                                            #INFO, curl_easy_strerror(_getinfoerr)); \
     418              :                         FAILACTION; \
     419              :                 } \
     420              :         } while (0)
     421              : 
     422              : /*
     423              :  * General JSON Parsing for OAuth Responses
     424              :  */
     425              : 
     426              : /*
     427              :  * Represents a single name/value pair in a JSON object. This is the primary
     428              :  * interface to parse_oauth_json().
     429              :  *
     430              :  * All fields are stored internally as strings or lists of strings, so clients
     431              :  * have to explicitly parse other scalar types (though they will have gone
     432              :  * through basic lexical validation). Storing nested objects is not currently
     433              :  * supported, nor is parsing arrays of anything other than strings.
     434              :  */
     435              : struct json_field
     436              : {
     437              :         const char *name;                       /* name (key) of the member */
     438              : 
     439              :         JsonTokenType type;                     /* currently supports JSON_TOKEN_STRING,
     440              :                                                                  * JSON_TOKEN_NUMBER, and
     441              :                                                                  * JSON_TOKEN_ARRAY_START */
     442              :         union
     443              :         {
     444              :                 char      **scalar;             /* for all scalar types */
     445              :                 struct curl_slist **array;      /* for type == JSON_TOKEN_ARRAY_START */
     446              :         };
     447              : 
     448              :         bool            required;               /* REQUIRED field, or just OPTIONAL? */
     449              : };
     450              : 
     451              : /* Documentation macros for json_field.required. */
     452              : #define PG_OAUTH_REQUIRED true
     453              : #define PG_OAUTH_OPTIONAL false
     454              : 
     455              : /* Parse state for parse_oauth_json(). */
     456              : struct oauth_parse
     457              : {
     458              :         PQExpBuffer errbuf;                     /* detail message for JSON_SEM_ACTION_FAILED */
     459              :         int                     nested;                 /* nesting level (zero is the top) */
     460              : 
     461              :         const struct json_field *fields;        /* field definition array */
     462              :         const struct json_field *active;        /* points inside the fields array */
     463              : };
     464              : 
     465              : #define oauth_parse_set_error(ctx, fmt, ...) \
     466              :         appendPQExpBuffer((ctx)->errbuf, libpq_gettext(fmt), ##__VA_ARGS__)
     467              : 
     468              : #define oauth_parse_set_error_internal(ctx, fmt, ...) \
     469              :         appendPQExpBuffer((ctx)->errbuf, fmt, ##__VA_ARGS__)
     470              : 
     471              : static void
     472            0 : report_type_mismatch(struct oauth_parse *ctx)
     473              : {
     474            0 :         char       *msgfmt;
     475              : 
     476            0 :         Assert(ctx->active);
     477              : 
     478              :         /*
     479              :          * At the moment, the only fields we're interested in are strings,
     480              :          * numbers, and arrays of strings.
     481              :          */
     482            0 :         switch (ctx->active->type)
     483              :         {
     484              :                 case JSON_TOKEN_STRING:
     485            0 :                         msgfmt = gettext_noop("field \"%s\" must be a string");
     486            0 :                         break;
     487              : 
     488              :                 case JSON_TOKEN_NUMBER:
     489            0 :                         msgfmt = gettext_noop("field \"%s\" must be a number");
     490            0 :                         break;
     491              : 
     492              :                 case JSON_TOKEN_ARRAY_START:
     493            0 :                         msgfmt = gettext_noop("field \"%s\" must be an array of strings");
     494            0 :                         break;
     495              : 
     496              :                 default:
     497            0 :                         Assert(false);
     498              :                         msgfmt = gettext_noop("field \"%s\" has unexpected type");
     499              :         }
     500              : 
     501            0 :         oauth_parse_set_error(ctx, msgfmt, ctx->active->name);
     502            0 : }
     503              : 
     504              : static JsonParseErrorType
     505            0 : oauth_json_object_start(void *state)
     506              : {
     507            0 :         struct oauth_parse *ctx = state;
     508              : 
     509            0 :         if (ctx->active)
     510              :         {
     511              :                 /*
     512              :                  * Currently, none of the fields we're interested in can be or contain
     513              :                  * objects, so we can reject this case outright.
     514              :                  */
     515            0 :                 report_type_mismatch(ctx);
     516            0 :                 return JSON_SEM_ACTION_FAILED;
     517              :         }
     518              : 
     519            0 :         ++ctx->nested;
     520            0 :         if (ctx->nested > MAX_OAUTH_NESTING_LEVEL)
     521              :         {
     522            0 :                 oauth_parse_set_error(ctx, "JSON is too deeply nested");
     523            0 :                 return JSON_SEM_ACTION_FAILED;
     524              :         }
     525              : 
     526            0 :         return JSON_SUCCESS;
     527            0 : }
     528              : 
     529              : static JsonParseErrorType
     530            0 : oauth_json_object_field_start(void *state, char *name, bool isnull)
     531              : {
     532            0 :         struct oauth_parse *ctx = state;
     533              : 
     534              :         /* We care only about the top-level fields. */
     535            0 :         if (ctx->nested == 1)
     536              :         {
     537            0 :                 const struct json_field *field = ctx->fields;
     538              : 
     539              :                 /*
     540              :                  * We should never start parsing a new field while a previous one is
     541              :                  * still active.
     542              :                  */
     543            0 :                 if (ctx->active)
     544              :                 {
     545            0 :                         Assert(false);
     546              :                         oauth_parse_set_error_internal(ctx,
     547              :                                                                                    "internal error: started field \"%s\" before field \"%s\" was finished",
     548              :                                                                                    name, ctx->active->name);
     549              :                         return JSON_SEM_ACTION_FAILED;
     550              :                 }
     551              : 
     552            0 :                 while (field->name)
     553              :                 {
     554            0 :                         if (strcmp(name, field->name) == 0)
     555              :                         {
     556            0 :                                 ctx->active = field;
     557            0 :                                 break;
     558              :                         }
     559              : 
     560            0 :                         ++field;
     561              :                 }
     562              : 
     563              :                 /*
     564              :                  * We don't allow duplicate field names; error out if the target has
     565              :                  * already been set.
     566              :                  */
     567            0 :                 if (ctx->active)
     568              :                 {
     569            0 :                         field = ctx->active;
     570              : 
     571            0 :                         if ((field->type == JSON_TOKEN_ARRAY_START && *field->array)
     572            0 :                                 || (field->type != JSON_TOKEN_ARRAY_START && *field->scalar))
     573              :                         {
     574            0 :                                 oauth_parse_set_error(ctx, "field \"%s\" is duplicated",
     575              :                                                                           field->name);
     576            0 :                                 return JSON_SEM_ACTION_FAILED;
     577              :                         }
     578            0 :                 }
     579            0 :         }
     580              : 
     581            0 :         return JSON_SUCCESS;
     582            0 : }
     583              : 
     584              : static JsonParseErrorType
     585            0 : oauth_json_object_end(void *state)
     586              : {
     587            0 :         struct oauth_parse *ctx = state;
     588              : 
     589            0 :         --ctx->nested;
     590              : 
     591              :         /*
     592              :          * All fields should be fully processed by the end of the top-level
     593              :          * object.
     594              :          */
     595            0 :         if (!ctx->nested && ctx->active)
     596              :         {
     597            0 :                 Assert(false);
     598              :                 oauth_parse_set_error_internal(ctx,
     599              :                                                                            "internal error: field \"%s\" still active at end of object",
     600              :                                                                            ctx->active->name);
     601              :                 return JSON_SEM_ACTION_FAILED;
     602              :         }
     603              : 
     604            0 :         return JSON_SUCCESS;
     605            0 : }
     606              : 
     607              : static JsonParseErrorType
     608            0 : oauth_json_array_start(void *state)
     609              : {
     610            0 :         struct oauth_parse *ctx = state;
     611              : 
     612            0 :         if (!ctx->nested)
     613              :         {
     614            0 :                 oauth_parse_set_error(ctx, "top-level element must be an object");
     615            0 :                 return JSON_SEM_ACTION_FAILED;
     616              :         }
     617              : 
     618            0 :         if (ctx->active)
     619              :         {
     620            0 :                 if (ctx->active->type != JSON_TOKEN_ARRAY_START
     621              :                 /* The arrays we care about must not have arrays as values. */
     622            0 :                         || ctx->nested > 1)
     623              :                 {
     624            0 :                         report_type_mismatch(ctx);
     625            0 :                         return JSON_SEM_ACTION_FAILED;
     626              :                 }
     627            0 :         }
     628              : 
     629            0 :         ++ctx->nested;
     630            0 :         if (ctx->nested > MAX_OAUTH_NESTING_LEVEL)
     631              :         {
     632            0 :                 oauth_parse_set_error(ctx, "JSON is too deeply nested");
     633            0 :                 return JSON_SEM_ACTION_FAILED;
     634              :         }
     635              : 
     636            0 :         return JSON_SUCCESS;
     637            0 : }
     638              : 
     639              : static JsonParseErrorType
     640            0 : oauth_json_array_end(void *state)
     641              : {
     642            0 :         struct oauth_parse *ctx = state;
     643              : 
     644            0 :         if (ctx->active)
     645              :         {
     646              :                 /*
     647              :                  * Clear the target (which should be an array inside the top-level
     648              :                  * object). For this to be safe, no target arrays can contain other
     649              :                  * arrays; we check for that in the array_start callback.
     650              :                  */
     651            0 :                 if (ctx->nested != 2 || ctx->active->type != JSON_TOKEN_ARRAY_START)
     652              :                 {
     653            0 :                         Assert(false);
     654              :                         oauth_parse_set_error_internal(ctx,
     655              :                                                                                    "internal error: found unexpected array end while parsing field \"%s\"",
     656              :                                                                                    ctx->active->name);
     657              :                         return JSON_SEM_ACTION_FAILED;
     658              :                 }
     659              : 
     660            0 :                 ctx->active = NULL;
     661            0 :         }
     662              : 
     663            0 :         --ctx->nested;
     664            0 :         return JSON_SUCCESS;
     665            0 : }
     666              : 
     667              : static JsonParseErrorType
     668            0 : oauth_json_scalar(void *state, char *token, JsonTokenType type)
     669              : {
     670            0 :         struct oauth_parse *ctx = state;
     671              : 
     672            0 :         if (!ctx->nested)
     673              :         {
     674            0 :                 oauth_parse_set_error(ctx, "top-level element must be an object");
     675            0 :                 return JSON_SEM_ACTION_FAILED;
     676              :         }
     677              : 
     678            0 :         if (ctx->active)
     679              :         {
     680            0 :                 const struct json_field *field = ctx->active;
     681            0 :                 JsonTokenType expected = field->type;
     682              : 
     683              :                 /* Make sure this matches what the active field expects. */
     684            0 :                 if (expected == JSON_TOKEN_ARRAY_START)
     685              :                 {
     686              :                         /* Are we actually inside an array? */
     687            0 :                         if (ctx->nested < 2)
     688              :                         {
     689            0 :                                 report_type_mismatch(ctx);
     690            0 :                                 return JSON_SEM_ACTION_FAILED;
     691              :                         }
     692              : 
     693              :                         /* Currently, arrays can only contain strings. */
     694            0 :                         expected = JSON_TOKEN_STRING;
     695            0 :                 }
     696              : 
     697            0 :                 if (type != expected)
     698              :                 {
     699            0 :                         report_type_mismatch(ctx);
     700            0 :                         return JSON_SEM_ACTION_FAILED;
     701              :                 }
     702              : 
     703            0 :                 if (field->type != JSON_TOKEN_ARRAY_START)
     704              :                 {
     705              :                         /* Ensure that we're parsing the top-level keys... */
     706            0 :                         if (ctx->nested != 1)
     707              :                         {
     708            0 :                                 Assert(false);
     709              :                                 oauth_parse_set_error_internal(ctx,
     710              :                                                                                            "internal error: scalar target found at nesting level %d",
     711              :                                                                                            ctx->nested);
     712              :                                 return JSON_SEM_ACTION_FAILED;
     713              :                         }
     714              : 
     715              :                         /* ...and that a result has not already been set. */
     716            0 :                         if (*field->scalar)
     717              :                         {
     718            0 :                                 Assert(false);
     719              :                                 oauth_parse_set_error_internal(ctx,
     720              :                                                                                            "internal error: scalar field \"%s\" would be assigned twice",
     721              :                                                                                            ctx->active->name);
     722              :                                 return JSON_SEM_ACTION_FAILED;
     723              :                         }
     724              : 
     725            0 :                         *field->scalar = strdup(token);
     726            0 :                         if (!*field->scalar)
     727            0 :                                 return JSON_OUT_OF_MEMORY;
     728              : 
     729            0 :                         ctx->active = NULL;
     730              : 
     731            0 :                         return JSON_SUCCESS;
     732              :                 }
     733              :                 else
     734              :                 {
     735            0 :                         struct curl_slist *temp;
     736              : 
     737              :                         /* The target array should be inside the top-level object. */
     738            0 :                         if (ctx->nested != 2)
     739              :                         {
     740            0 :                                 Assert(false);
     741              :                                 oauth_parse_set_error_internal(ctx,
     742              :                                                                                            "internal error: array member found at nesting level %d",
     743              :                                                                                            ctx->nested);
     744              :                                 return JSON_SEM_ACTION_FAILED;
     745              :                         }
     746              : 
     747              :                         /* Note that curl_slist_append() makes a copy of the token. */
     748            0 :                         temp = curl_slist_append(*field->array, token);
     749            0 :                         if (!temp)
     750            0 :                                 return JSON_OUT_OF_MEMORY;
     751              : 
     752            0 :                         *field->array = temp;
     753            0 :                 }
     754            0 :         }
     755              :         else
     756              :         {
     757              :                 /* otherwise we just ignore it */
     758              :         }
     759              : 
     760            0 :         return JSON_SUCCESS;
     761            0 : }
     762              : 
     763              : /*
     764              :  * Checks the Content-Type header against the expected type. Parameters are
     765              :  * allowed but ignored.
     766              :  */
     767              : static bool
     768            0 : check_content_type(struct async_ctx *actx, const char *type)
     769              : {
     770            0 :         const size_t type_len = strlen(type);
     771            0 :         char       *content_type;
     772              : 
     773            0 :         CHECK_GETINFO(actx, CURLINFO_CONTENT_TYPE, &content_type, return false);
     774              : 
     775            0 :         if (!content_type)
     776              :         {
     777            0 :                 actx_error(actx, "no content type was provided");
     778            0 :                 return false;
     779              :         }
     780              : 
     781              :         /*
     782              :          * We need to perform a length limited comparison and not compare the
     783              :          * whole string.
     784              :          */
     785            0 :         if (pg_strncasecmp(content_type, type, type_len) != 0)
     786            0 :                 goto fail;
     787              : 
     788              :         /* On an exact match, we're done. */
     789            0 :         Assert(strlen(content_type) >= type_len);
     790            0 :         if (content_type[type_len] == '\0')
     791            0 :                 return true;
     792              : 
     793              :         /*
     794              :          * Only a semicolon (optionally preceded by HTTP optional whitespace) is
     795              :          * acceptable after the prefix we checked. This marks the start of media
     796              :          * type parameters, which we currently have no use for.
     797              :          */
     798            0 :         for (size_t i = type_len; content_type[i]; ++i)
     799              :         {
     800            0 :                 switch (content_type[i])
     801              :                 {
     802              :                         case ';':
     803            0 :                                 return true;    /* success! */
     804              : 
     805              :                         case ' ':
     806              :                         case '\t':
     807              :                                 /* HTTP optional whitespace allows only spaces and htabs. */
     808            0 :                                 break;
     809              : 
     810              :                         default:
     811            0 :                                 goto fail;
     812              :                 }
     813            0 :         }
     814              : 
     815              : fail:
     816            0 :         actx_error(actx, "unexpected content type: \"%s\"", content_type);
     817            0 :         return false;
     818            0 : }
     819              : 
     820              : /*
     821              :  * A helper function for general JSON parsing. fields is the array of field
     822              :  * definitions with their backing pointers. The response will be parsed from
     823              :  * actx->curl and actx->work_data (as set up by start_request()), and any
     824              :  * parsing errors will be placed into actx->errbuf.
     825              :  */
     826              : static bool
     827            0 : parse_oauth_json(struct async_ctx *actx, const struct json_field *fields)
     828              : {
     829            0 :         PQExpBuffer resp = &actx->work_data;
     830            0 :         JsonLexContext lex = {0};
     831            0 :         JsonSemAction sem = {0};
     832            0 :         JsonParseErrorType err;
     833            0 :         struct oauth_parse ctx = {0};
     834            0 :         bool            success = false;
     835              : 
     836            0 :         if (!check_content_type(actx, "application/json"))
     837            0 :                 return false;
     838              : 
     839            0 :         if (strlen(resp->data) != resp->len)
     840              :         {
     841            0 :                 actx_error(actx, "response contains embedded NULLs");
     842            0 :                 return false;
     843              :         }
     844              : 
     845              :         /*
     846              :          * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
     847              :          * that up front.
     848              :          */
     849            0 :         if (pg_encoding_verifymbstr(PG_UTF8, resp->data, resp->len) != resp->len)
     850              :         {
     851            0 :                 actx_error(actx, "response is not valid UTF-8");
     852            0 :                 return false;
     853              :         }
     854              : 
     855            0 :         makeJsonLexContextCstringLen(&lex, resp->data, resp->len, PG_UTF8, true);
     856            0 :         setJsonLexContextOwnsTokens(&lex, true);    /* must not leak on error */
     857              : 
     858            0 :         ctx.errbuf = &actx->errbuf;
     859            0 :         ctx.fields = fields;
     860            0 :         sem.semstate = &ctx;
     861              : 
     862            0 :         sem.object_start = oauth_json_object_start;
     863            0 :         sem.object_field_start = oauth_json_object_field_start;
     864            0 :         sem.object_end = oauth_json_object_end;
     865            0 :         sem.array_start = oauth_json_array_start;
     866            0 :         sem.array_end = oauth_json_array_end;
     867            0 :         sem.scalar = oauth_json_scalar;
     868              : 
     869            0 :         err = pg_parse_json(&lex, &sem);
     870              : 
     871            0 :         if (err != JSON_SUCCESS)
     872              :         {
     873              :                 /*
     874              :                  * For JSON_SEM_ACTION_FAILED, we've already written the error
     875              :                  * message. Other errors come directly from pg_parse_json(), already
     876              :                  * translated.
     877              :                  */
     878            0 :                 if (err != JSON_SEM_ACTION_FAILED)
     879            0 :                         actx_error_str(actx, json_errdetail(err, &lex));
     880              : 
     881            0 :                 goto cleanup;
     882              :         }
     883              : 
     884              :         /* Check all required fields. */
     885            0 :         while (fields->name)
     886              :         {
     887            0 :                 if (fields->required
     888            0 :                         && !*fields->scalar
     889            0 :                         && !*fields->array)
     890              :                 {
     891            0 :                         actx_error(actx, "field \"%s\" is missing", fields->name);
     892            0 :                         goto cleanup;
     893              :                 }
     894              : 
     895            0 :                 fields++;
     896              :         }
     897              : 
     898            0 :         success = true;
     899              : 
     900              : cleanup:
     901            0 :         freeJsonLexContext(&lex);
     902            0 :         return success;
     903            0 : }
     904              : 
     905              : /*
     906              :  * JSON Parser Definitions
     907              :  */
     908              : 
     909              : /*
     910              :  * Parses authorization server metadata. Fields are defined by OIDC Discovery
     911              :  * 1.0 and RFC 8414.
     912              :  */
     913              : static bool
     914            0 : parse_provider(struct async_ctx *actx, struct provider *provider)
     915              : {
     916            0 :         struct json_field fields[] = {
     917            0 :                 {"issuer", JSON_TOKEN_STRING, {&provider->issuer}, PG_OAUTH_REQUIRED},
     918            0 :                 {"token_endpoint", JSON_TOKEN_STRING, {&provider->token_endpoint}, PG_OAUTH_REQUIRED},
     919              : 
     920              :                 /*----
     921              :                  * The following fields are technically REQUIRED, but we don't use
     922              :                  * them anywhere yet:
     923              :                  *
     924              :                  * - jwks_uri
     925              :                  * - response_types_supported
     926              :                  * - subject_types_supported
     927              :                  * - id_token_signing_alg_values_supported
     928              :                  */
     929              : 
     930            0 :                 {"device_authorization_endpoint", JSON_TOKEN_STRING, {&provider->device_authorization_endpoint}, PG_OAUTH_OPTIONAL},
     931            0 :                 {"grant_types_supported", JSON_TOKEN_ARRAY_START, {.array = &provider->grant_types_supported}, PG_OAUTH_OPTIONAL},
     932              : 
     933            0 :                 {0},
     934              :         };
     935              : 
     936            0 :         return parse_oauth_json(actx, fields);
     937            0 : }
     938              : 
     939              : /*
     940              :  * Parses a valid JSON number into a double. The input must have come from
     941              :  * pg_parse_json(), so that we know the lexer has validated it; there's no
     942              :  * in-band signal for invalid formats.
     943              :  */
     944              : static double
     945            0 : parse_json_number(const char *s)
     946              : {
     947            0 :         double          parsed;
     948            0 :         int                     cnt;
     949              : 
     950              :         /*
     951              :          * The JSON lexer has already validated the number, which is stricter than
     952              :          * the %f format, so we should be good to use sscanf().
     953              :          */
     954            0 :         cnt = sscanf(s, "%lf", &parsed);
     955              : 
     956            0 :         if (cnt != 1)
     957              :         {
     958              :                 /*
     959              :                  * Either the lexer screwed up or our assumption above isn't true, and
     960              :                  * either way a developer needs to take a look.
     961              :                  */
     962            0 :                 Assert(false);
     963              :                 return 0;
     964              :         }
     965              : 
     966            0 :         return parsed;
     967            0 : }
     968              : 
     969              : /*
     970              :  * Parses the "interval" JSON number, corresponding to the number of seconds to
     971              :  * wait between token endpoint requests.
     972              :  *
     973              :  * RFC 8628 is pretty silent on sanity checks for the interval. As a matter of
     974              :  * practicality, round any fractional intervals up to the next second, and clamp
     975              :  * the result at a minimum of one. (Zero-second intervals would result in an
     976              :  * expensive network polling loop.) Tests may remove the lower bound with
     977              :  * PGOAUTHDEBUG, for improved performance.
     978              :  */
     979              : static int
     980            0 : parse_interval(struct async_ctx *actx, const char *interval_str)
     981              : {
     982            0 :         double          parsed;
     983              : 
     984            0 :         parsed = parse_json_number(interval_str);
     985            0 :         parsed = ceil(parsed);
     986              : 
     987            0 :         if (parsed < 1)
     988            0 :                 return actx->debugging ? 0 : 1;
     989              : 
     990            0 :         else if (parsed >= INT_MAX)
     991            0 :                 return INT_MAX;
     992              : 
     993            0 :         return parsed;
     994            0 : }
     995              : 
     996              : /*
     997              :  * Parses the "expires_in" JSON number, corresponding to the number of seconds
     998              :  * remaining in the lifetime of the device code request.
     999              :  *
    1000              :  * Similar to parse_interval, but we have even fewer requirements for reasonable
    1001              :  * values since we don't use the expiration time directly (it's passed to the
    1002              :  * PQAUTHDATA_PROMPT_OAUTH_DEVICE hook, in case the application wants to do
    1003              :  * something with it). We simply round down and clamp to int range.
    1004              :  */
    1005              : static int
    1006            0 : parse_expires_in(struct async_ctx *actx, const char *expires_in_str)
    1007              : {
    1008            0 :         double          parsed;
    1009              : 
    1010            0 :         parsed = parse_json_number(expires_in_str);
    1011            0 :         parsed = floor(parsed);
    1012              : 
    1013            0 :         if (parsed >= INT_MAX)
    1014            0 :                 return INT_MAX;
    1015            0 :         else if (parsed <= INT_MIN)
    1016            0 :                 return INT_MIN;
    1017              : 
    1018            0 :         return parsed;
    1019            0 : }
    1020              : 
    1021              : /*
    1022              :  * Parses the Device Authorization Response (RFC 8628, Sec. 3.2).
    1023              :  */
    1024              : static bool
    1025            0 : parse_device_authz(struct async_ctx *actx, struct device_authz *authz)
    1026              : {
    1027            0 :         struct json_field fields[] = {
    1028            0 :                 {"device_code", JSON_TOKEN_STRING, {&authz->device_code}, PG_OAUTH_REQUIRED},
    1029            0 :                 {"user_code", JSON_TOKEN_STRING, {&authz->user_code}, PG_OAUTH_REQUIRED},
    1030            0 :                 {"verification_uri", JSON_TOKEN_STRING, {&authz->verification_uri}, PG_OAUTH_REQUIRED},
    1031            0 :                 {"expires_in", JSON_TOKEN_NUMBER, {&authz->expires_in_str}, PG_OAUTH_REQUIRED},
    1032              : 
    1033              :                 /*
    1034              :                  * Some services (Google, Azure) spell verification_uri differently.
    1035              :                  * We accept either.
    1036              :                  */
    1037            0 :                 {"verification_url", JSON_TOKEN_STRING, {&authz->verification_uri}, PG_OAUTH_REQUIRED},
    1038              : 
    1039              :                 /*
    1040              :                  * There is no evidence of verification_uri_complete being spelled
    1041              :                  * with "url" instead with any service provider, so only support
    1042              :                  * "uri".
    1043              :                  */
    1044            0 :                 {"verification_uri_complete", JSON_TOKEN_STRING, {&authz->verification_uri_complete}, PG_OAUTH_OPTIONAL},
    1045            0 :                 {"interval", JSON_TOKEN_NUMBER, {&authz->interval_str}, PG_OAUTH_OPTIONAL},
    1046              : 
    1047            0 :                 {0},
    1048              :         };
    1049              : 
    1050            0 :         if (!parse_oauth_json(actx, fields))
    1051            0 :                 return false;
    1052              : 
    1053              :         /*
    1054              :          * Parse our numeric fields. Lexing has already completed by this time, so
    1055              :          * we at least know they're valid JSON numbers.
    1056              :          */
    1057            0 :         if (authz->interval_str)
    1058            0 :                 authz->interval = parse_interval(actx, authz->interval_str);
    1059              :         else
    1060              :         {
    1061              :                 /*
    1062              :                  * RFC 8628 specifies 5 seconds as the default value if the server
    1063              :                  * doesn't provide an interval.
    1064              :                  */
    1065            0 :                 authz->interval = 5;
    1066              :         }
    1067              : 
    1068            0 :         Assert(authz->expires_in_str);       /* ensured by parse_oauth_json() */
    1069            0 :         authz->expires_in = parse_expires_in(actx, authz->expires_in_str);
    1070              : 
    1071            0 :         return true;
    1072            0 : }
    1073              : 
    1074              : /*
    1075              :  * Parses the device access token error response (RFC 8628, Sec. 3.5, which
    1076              :  * uses the error response defined in RFC 6749, Sec. 5.2).
    1077              :  */
    1078              : static bool
    1079            0 : parse_token_error(struct async_ctx *actx, struct token_error *err)
    1080              : {
    1081            0 :         bool            result;
    1082            0 :         struct json_field fields[] = {
    1083            0 :                 {"error", JSON_TOKEN_STRING, {&err->error}, PG_OAUTH_REQUIRED},
    1084              : 
    1085            0 :                 {"error_description", JSON_TOKEN_STRING, {&err->error_description}, PG_OAUTH_OPTIONAL},
    1086              : 
    1087            0 :                 {0},
    1088              :         };
    1089              : 
    1090            0 :         result = parse_oauth_json(actx, fields);
    1091              : 
    1092              :         /*
    1093              :          * Since token errors are parsed during other active error paths, only
    1094              :          * override the errctx if parsing explicitly fails.
    1095              :          */
    1096            0 :         if (!result)
    1097            0 :                 actx->errctx = libpq_gettext("failed to parse token error response");
    1098              : 
    1099            0 :         return result;
    1100            0 : }
    1101              : 
    1102              : /*
    1103              :  * Constructs a message from the token error response and puts it into
    1104              :  * actx->errbuf.
    1105              :  */
    1106              : static void
    1107            0 : record_token_error(struct async_ctx *actx, const struct token_error *err)
    1108              : {
    1109            0 :         if (err->error_description)
    1110            0 :                 appendPQExpBuffer(&actx->errbuf, "%s ", err->error_description);
    1111              :         else
    1112              :         {
    1113              :                 /*
    1114              :                  * Try to get some more helpful detail into the error string. A 401
    1115              :                  * status in particular implies that the oauth_client_secret is
    1116              :                  * missing or wrong.
    1117              :                  */
    1118            0 :                 long            response_code;
    1119              : 
    1120            0 :                 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, response_code = 0);
    1121              : 
    1122            0 :                 if (response_code == 401)
    1123              :                 {
    1124            0 :                         actx_error(actx, actx->used_basic_auth
    1125              :                                            ? gettext_noop("provider rejected the oauth_client_secret")
    1126              :                                            : gettext_noop("provider requires client authentication, and no oauth_client_secret is set"));
    1127            0 :                         actx_error_str(actx, " ");
    1128            0 :                 }
    1129            0 :         }
    1130              : 
    1131            0 :         appendPQExpBuffer(&actx->errbuf, "(%s)", err->error);
    1132            0 : }
    1133              : 
    1134              : /*
    1135              :  * Parses the device access token response (RFC 8628, Sec. 3.5, which uses the
    1136              :  * success response defined in RFC 6749, Sec. 5.1).
    1137              :  */
    1138              : static bool
    1139            0 : parse_access_token(struct async_ctx *actx, struct token *tok)
    1140              : {
    1141            0 :         struct json_field fields[] = {
    1142            0 :                 {"access_token", JSON_TOKEN_STRING, {&tok->access_token}, PG_OAUTH_REQUIRED},
    1143            0 :                 {"token_type", JSON_TOKEN_STRING, {&tok->token_type}, PG_OAUTH_REQUIRED},
    1144              : 
    1145              :                 /*---
    1146              :                  * We currently have no use for the following OPTIONAL fields:
    1147              :                  *
    1148              :                  * - expires_in: This will be important for maintaining a token cache,
    1149              :                  *               but we do not yet implement one.
    1150              :                  *
    1151              :                  * - refresh_token: Ditto.
    1152              :                  *
    1153              :                  * - scope: This is only sent when the authorization server sees fit to
    1154              :                  *          change our scope request. It's not clear what we should do
    1155              :                  *          about this; either it's been done as a matter of policy, or
    1156              :                  *          the user has explicitly denied part of the authorization,
    1157              :                  *          and either way the server-side validator is in a better
    1158              :                  *          place to complain if the change isn't acceptable.
    1159              :                  */
    1160              : 
    1161            0 :                 {0},
    1162              :         };
    1163              : 
    1164            0 :         return parse_oauth_json(actx, fields);
    1165            0 : }
    1166              : 
    1167              : /*
    1168              :  * libcurl Multi Setup/Callbacks
    1169              :  */
    1170              : 
    1171              : /*
    1172              :  * Sets up the actx->mux, which is the altsock that PQconnectPoll clients will
    1173              :  * select() on instead of the Postgres socket during OAuth negotiation.
    1174              :  *
    1175              :  * This is just an epoll set or kqueue abstracting multiple other descriptors.
    1176              :  * For epoll, the timerfd is always part of the set; it's just disabled when
    1177              :  * we're not using it. For kqueue, the "timerfd" is actually a second kqueue
    1178              :  * instance which is only added to the set when needed.
    1179              :  */
    1180              : static bool
    1181            0 : setup_multiplexer(struct async_ctx *actx)
    1182              : {
    1183              : #if defined(HAVE_SYS_EPOLL_H)
    1184              :         struct epoll_event ev = {.events = EPOLLIN};
    1185              : 
    1186              :         actx->mux = epoll_create1(EPOLL_CLOEXEC);
    1187              :         if (actx->mux < 0)
    1188              :         {
    1189              :                 actx_error_internal(actx, "failed to create epoll set: %m");
    1190              :                 return false;
    1191              :         }
    1192              : 
    1193              :         actx->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
    1194              :         if (actx->timerfd < 0)
    1195              :         {
    1196              :                 actx_error_internal(actx, "failed to create timerfd: %m");
    1197              :                 return false;
    1198              :         }
    1199              : 
    1200              :         if (epoll_ctl(actx->mux, EPOLL_CTL_ADD, actx->timerfd, &ev) < 0)
    1201              :         {
    1202              :                 actx_error_internal(actx, "failed to add timerfd to epoll set: %m");
    1203              :                 return false;
    1204              :         }
    1205              : 
    1206              :         return true;
    1207              : #elif defined(HAVE_SYS_EVENT_H)
    1208            0 :         actx->mux = kqueue();
    1209            0 :         if (actx->mux < 0)
    1210              :         {
    1211            0 :                 actx_error_internal(actx, "failed to create kqueue: %m");
    1212            0 :                 return false;
    1213              :         }
    1214              : 
    1215              :         /*
    1216              :          * Originally, we set EVFILT_TIMER directly on the top-level multiplexer.
    1217              :          * This makes it difficult to implement timer_expired(), though, so now we
    1218              :          * set EVFILT_TIMER on a separate actx->timerfd, which is chained to
    1219              :          * actx->mux while the timer is active.
    1220              :          */
    1221            0 :         actx->timerfd = kqueue();
    1222            0 :         if (actx->timerfd < 0)
    1223              :         {
    1224            0 :                 actx_error_internal(actx, "failed to create timer kqueue: %m");
    1225            0 :                 return false;
    1226              :         }
    1227              : 
    1228            0 :         return true;
    1229              : #else
    1230              : #error setup_multiplexer is not implemented on this platform
    1231              : #endif
    1232            0 : }
    1233              : 
    1234              : /*
    1235              :  * Adds and removes sockets from the multiplexer set, as directed by the
    1236              :  * libcurl multi handle.
    1237              :  */
    1238              : static int
    1239            0 : register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
    1240              :                                 void *socketp)
    1241              : {
    1242            0 :         struct async_ctx *actx = ctx;
    1243              : 
    1244              : #if defined(HAVE_SYS_EPOLL_H)
    1245              :         struct epoll_event ev = {0};
    1246              :         int                     res;
    1247              :         int                     op = EPOLL_CTL_ADD;
    1248              : 
    1249              :         switch (what)
    1250              :         {
    1251              :                 case CURL_POLL_IN:
    1252              :                         ev.events = EPOLLIN;
    1253              :                         break;
    1254              : 
    1255              :                 case CURL_POLL_OUT:
    1256              :                         ev.events = EPOLLOUT;
    1257              :                         break;
    1258              : 
    1259              :                 case CURL_POLL_INOUT:
    1260              :                         ev.events = EPOLLIN | EPOLLOUT;
    1261              :                         break;
    1262              : 
    1263              :                 case CURL_POLL_REMOVE:
    1264              :                         op = EPOLL_CTL_DEL;
    1265              :                         break;
    1266              : 
    1267              :                 default:
    1268              :                         actx_error_internal(actx, "unknown libcurl socket operation: %d", what);
    1269              :                         return -1;
    1270              :         }
    1271              : 
    1272              :         res = epoll_ctl(actx->mux, op, socket, &ev);
    1273              :         if (res < 0 && errno == EEXIST)
    1274              :         {
    1275              :                 /* We already had this socket in the poll set. */
    1276              :                 op = EPOLL_CTL_MOD;
    1277              :                 res = epoll_ctl(actx->mux, op, socket, &ev);
    1278              :         }
    1279              : 
    1280              :         if (res < 0)
    1281              :         {
    1282              :                 switch (op)
    1283              :                 {
    1284              :                         case EPOLL_CTL_ADD:
    1285              :                                 actx_error_internal(actx, "could not add to epoll set: %m");
    1286              :                                 break;
    1287              : 
    1288              :                         case EPOLL_CTL_DEL:
    1289              :                                 actx_error_internal(actx, "could not delete from epoll set: %m");
    1290              :                                 break;
    1291              : 
    1292              :                         default:
    1293              :                                 actx_error_internal(actx, "could not update epoll set: %m");
    1294              :                 }
    1295              : 
    1296              :                 return -1;
    1297              :         }
    1298              : 
    1299              :         return 0;
    1300              : #elif defined(HAVE_SYS_EVENT_H)
    1301            0 :         struct kevent ev[2];
    1302            0 :         struct kevent ev_out[2];
    1303            0 :         struct timespec timeout = {0};
    1304            0 :         int                     nev = 0;
    1305            0 :         int                     res;
    1306              : 
    1307              :         /*
    1308              :          * We don't know which of the events is currently registered, perhaps
    1309              :          * both, so we always try to remove unneeded events. This means we need to
    1310              :          * tolerate ENOENT below.
    1311              :          */
    1312            0 :         switch (what)
    1313              :         {
    1314              :                 case CURL_POLL_IN:
    1315            0 :                         EV_SET(&ev[nev], socket, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, 0);
    1316            0 :                         nev++;
    1317            0 :                         EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_DELETE | EV_RECEIPT, 0, 0, 0);
    1318            0 :                         nev++;
    1319            0 :                         break;
    1320              : 
    1321              :                 case CURL_POLL_OUT:
    1322            0 :                         EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_ADD | EV_RECEIPT, 0, 0, 0);
    1323            0 :                         nev++;
    1324            0 :                         EV_SET(&ev[nev], socket, EVFILT_READ, EV_DELETE | EV_RECEIPT, 0, 0, 0);
    1325            0 :                         nev++;
    1326            0 :                         break;
    1327              : 
    1328              :                 case CURL_POLL_INOUT:
    1329            0 :                         EV_SET(&ev[nev], socket, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, 0);
    1330            0 :                         nev++;
    1331            0 :                         EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_ADD | EV_RECEIPT, 0, 0, 0);
    1332            0 :                         nev++;
    1333            0 :                         break;
    1334              : 
    1335              :                 case CURL_POLL_REMOVE:
    1336            0 :                         EV_SET(&ev[nev], socket, EVFILT_READ, EV_DELETE | EV_RECEIPT, 0, 0, 0);
    1337            0 :                         nev++;
    1338            0 :                         EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_DELETE | EV_RECEIPT, 0, 0, 0);
    1339            0 :                         nev++;
    1340            0 :                         break;
    1341              : 
    1342              :                 default:
    1343            0 :                         actx_error_internal(actx, "unknown libcurl socket operation: %d", what);
    1344            0 :                         return -1;
    1345              :         }
    1346              : 
    1347            0 :         Assert(nev <= lengthof(ev));
    1348            0 :         Assert(nev <= lengthof(ev_out));
    1349              : 
    1350            0 :         res = kevent(actx->mux, ev, nev, ev_out, nev, &timeout);
    1351            0 :         if (res < 0)
    1352              :         {
    1353            0 :                 actx_error_internal(actx, "could not modify kqueue: %m");
    1354            0 :                 return -1;
    1355              :         }
    1356              : 
    1357              :         /*
    1358              :          * We can't use the simple errno version of kevent, because we need to
    1359              :          * skip over ENOENT while still allowing a second change to be processed.
    1360              :          * So we need a longer-form error checking loop.
    1361              :          */
    1362            0 :         for (int i = 0; i < res; ++i)
    1363              :         {
    1364              :                 /*
    1365              :                  * EV_RECEIPT should guarantee one EV_ERROR result for every change,
    1366              :                  * whether successful or not. Failed entries contain a non-zero errno
    1367              :                  * in the data field.
    1368              :                  */
    1369            0 :                 Assert(ev_out[i].flags & EV_ERROR);
    1370              : 
    1371            0 :                 errno = ev_out[i].data;
    1372            0 :                 if (errno && errno != ENOENT)
    1373              :                 {
    1374            0 :                         switch (what)
    1375              :                         {
    1376              :                                 case CURL_POLL_REMOVE:
    1377            0 :                                         actx_error_internal(actx, "could not delete from kqueue: %m");
    1378            0 :                                         break;
    1379              :                                 default:
    1380            0 :                                         actx_error_internal(actx, "could not add to kqueue: %m");
    1381            0 :                         }
    1382            0 :                         return -1;
    1383              :                 }
    1384            0 :         }
    1385              : 
    1386            0 :         return 0;
    1387              : #else
    1388              : #error register_socket is not implemented on this platform
    1389              : #endif
    1390            0 : }
    1391              : 
    1392              : /*
    1393              :  * If there is no work to do on any of the descriptors in the multiplexer, then
    1394              :  * this function must ensure that the multiplexer is not readable.
    1395              :  *
    1396              :  * Unlike epoll descriptors, kqueue descriptors only transition from readable to
    1397              :  * unreadable when kevent() is called and finds nothing, after removing
    1398              :  * level-triggered conditions that have gone away. We therefore need a dummy
    1399              :  * kevent() call after operations might have been performed on the monitored
    1400              :  * sockets or timer_fd. Any event returned is ignored here, but it also remains
    1401              :  * queued (being level-triggered) and leaves the descriptor readable. This is a
    1402              :  * no-op for epoll descriptors.
    1403              :  */
    1404              : static bool
    1405            0 : comb_multiplexer(struct async_ctx *actx)
    1406              : {
    1407              : #if defined(HAVE_SYS_EPOLL_H)
    1408              :         /* The epoll implementation doesn't hold onto stale events. */
    1409              :         return true;
    1410              : #elif defined(HAVE_SYS_EVENT_H)
    1411            0 :         struct timespec timeout = {0};
    1412            0 :         struct kevent ev;
    1413              : 
    1414              :         /*
    1415              :          * Try to read a single pending event. We can actually ignore the result:
    1416              :          * either we found an event to process, in which case the multiplexer is
    1417              :          * correctly readable for that event at minimum, and it doesn't matter if
    1418              :          * there are any stale events; or we didn't find any, in which case the
    1419              :          * kernel will have discarded any stale events as it traveled to the end
    1420              :          * of the queue.
    1421              :          *
    1422              :          * Note that this depends on our registrations being level-triggered --
    1423              :          * even the timer, so we use a chained kqueue for that instead of an
    1424              :          * EVFILT_TIMER on the top-level mux. If we used edge-triggered events,
    1425              :          * this call would improperly discard them.
    1426              :          */
    1427            0 :         if (kevent(actx->mux, NULL, 0, &ev, 1, &timeout) < 0)
    1428              :         {
    1429            0 :                 actx_error_internal(actx, "could not comb kqueue: %m");
    1430            0 :                 return false;
    1431              :         }
    1432              : 
    1433            0 :         return true;
    1434              : #else
    1435              : #error comb_multiplexer is not implemented on this platform
    1436              : #endif
    1437            0 : }
    1438              : 
    1439              : /*
    1440              :  * Enables or disables the timer in the multiplexer set. The timeout value is
    1441              :  * in milliseconds (negative values disable the timer).
    1442              :  *
    1443              :  * For epoll, rather than continually adding and removing the timer, we keep it
    1444              :  * in the set at all times and just disarm it when it's not needed. For kqueue,
    1445              :  * the timer is removed completely when disabled to prevent stale timeouts from
    1446              :  * remaining in the queue.
    1447              :  *
    1448              :  * To meet Curl requirements for the CURLMOPT_TIMERFUNCTION, implementations of
    1449              :  * set_timer must handle repeated calls by fully discarding any previous running
    1450              :  * or expired timer.
    1451              :  */
    1452              : static bool
    1453            0 : set_timer(struct async_ctx *actx, long timeout)
    1454              : {
    1455              : #if defined(HAVE_SYS_EPOLL_H)
    1456              :         struct itimerspec spec = {0};
    1457              : 
    1458              :         if (timeout < 0)
    1459              :         {
    1460              :                 /* the zero itimerspec will disarm the timer below */
    1461              :         }
    1462              :         else if (timeout == 0)
    1463              :         {
    1464              :                 /*
    1465              :                  * A zero timeout means libcurl wants us to call back immediately.
    1466              :                  * That's not technically an option for timerfd, but we can make the
    1467              :                  * timeout ridiculously short.
    1468              :                  */
    1469              :                 spec.it_value.tv_nsec = 1;
    1470              :         }
    1471              :         else
    1472              :         {
    1473              :                 spec.it_value.tv_sec = timeout / 1000;
    1474              :                 spec.it_value.tv_nsec = (timeout % 1000) * 1000000;
    1475              :         }
    1476              : 
    1477              :         if (timerfd_settime(actx->timerfd, 0 /* no flags */ , &spec, NULL) < 0)
    1478              :         {
    1479              :                 actx_error_internal(actx, "setting timerfd to %ld: %m", timeout);
    1480              :                 return false;
    1481              :         }
    1482              : 
    1483              :         return true;
    1484              : #elif defined(HAVE_SYS_EVENT_H)
    1485            0 :         struct kevent ev;
    1486              : 
    1487              : #ifdef __NetBSD__
    1488              : 
    1489              :         /*
    1490              :          * Work around NetBSD's rejection of zero timeouts (EINVAL), a bit like
    1491              :          * timerfd above.
    1492              :          */
    1493              :         if (timeout == 0)
    1494              :                 timeout = 1;
    1495              : #endif
    1496              : 
    1497              :         /*
    1498              :          * Always disable the timer, and remove it from the multiplexer, to clear
    1499              :          * out any already-queued events. (On some BSDs, adding an EVFILT_TIMER to
    1500              :          * a kqueue that already has one will clear stale events, but not on
    1501              :          * macOS.)
    1502              :          *
    1503              :          * If there was no previous timer set, the kevent calls will result in
    1504              :          * ENOENT, which is fine.
    1505              :          */
    1506            0 :         EV_SET(&ev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, 0);
    1507            0 :         if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
    1508              :         {
    1509            0 :                 actx_error_internal(actx, "deleting kqueue timer: %m");
    1510            0 :                 return false;
    1511              :         }
    1512              : 
    1513            0 :         EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_DELETE, 0, 0, 0);
    1514            0 :         if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
    1515              :         {
    1516            0 :                 actx_error_internal(actx, "removing kqueue timer from multiplexer: %m");
    1517            0 :                 return false;
    1518              :         }
    1519              : 
    1520              :         /* If we're not adding a timer, we're done. */
    1521            0 :         if (timeout < 0)
    1522            0 :                 return true;
    1523              : 
    1524            0 :         EV_SET(&ev, 1, EVFILT_TIMER, (EV_ADD | EV_ONESHOT), 0, timeout, 0);
    1525            0 :         if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0)
    1526              :         {
    1527            0 :                 actx_error_internal(actx, "setting kqueue timer to %ld: %m", timeout);
    1528            0 :                 return false;
    1529              :         }
    1530              : 
    1531            0 :         EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_ADD, 0, 0, 0);
    1532            0 :         if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0)
    1533              :         {
    1534            0 :                 actx_error_internal(actx, "adding kqueue timer to multiplexer: %m");
    1535            0 :                 return false;
    1536              :         }
    1537              : 
    1538            0 :         return true;
    1539              : #else
    1540              : #error set_timer is not implemented on this platform
    1541              : #endif
    1542            0 : }
    1543              : 
    1544              : /*
    1545              :  * Returns 1 if the timeout in the multiplexer set has expired since the last
    1546              :  * call to set_timer(), 0 if the timer is either still running or disarmed, or
    1547              :  * -1 (with an actx_error() report) if the timer cannot be queried.
    1548              :  */
    1549              : static int
    1550            0 : timer_expired(struct async_ctx *actx)
    1551              : {
    1552              : #if defined(HAVE_SYS_EPOLL_H) || defined(HAVE_SYS_EVENT_H)
    1553            0 :         int                     res;
    1554              : 
    1555              :         /* Is the timer ready? */
    1556            0 :         res = PQsocketPoll(actx->timerfd, 1 /* forRead */ , 0, 0);
    1557            0 :         if (res < 0)
    1558              :         {
    1559            0 :                 actx_error(actx, "checking timer expiration: %m");
    1560            0 :                 return -1;
    1561              :         }
    1562              : 
    1563            0 :         return (res > 0);
    1564              : #else
    1565              : #error timer_expired is not implemented on this platform
    1566              : #endif
    1567            0 : }
    1568              : 
    1569              : /*
    1570              :  * Adds or removes timeouts from the multiplexer set, as directed by the
    1571              :  * libcurl multi handle.
    1572              :  */
    1573              : static int
    1574            0 : register_timer(CURLM *curlm, long timeout, void *ctx)
    1575              : {
    1576            0 :         struct async_ctx *actx = ctx;
    1577              : 
    1578              :         /*
    1579              :          * There might be an optimization opportunity here: if timeout == 0, we
    1580              :          * could signal drive_request to immediately call
    1581              :          * curl_multi_socket_action, rather than returning all the way up the
    1582              :          * stack only to come right back. But it's not clear that the additional
    1583              :          * code complexity is worth it.
    1584              :          */
    1585            0 :         if (!set_timer(actx, timeout))
    1586            0 :                 return -1;                              /* actx_error already called */
    1587              : 
    1588            0 :         return 0;
    1589            0 : }
    1590              : 
    1591              : /*
    1592              :  * Removes any expired-timer event from the multiplexer. If was_expired is not
    1593              :  * NULL, it will contain whether or not the timer was expired at time of call.
    1594              :  */
    1595              : static bool
    1596            0 : drain_timer_events(struct async_ctx *actx, bool *was_expired)
    1597              : {
    1598            0 :         int                     res;
    1599              : 
    1600            0 :         res = timer_expired(actx);
    1601            0 :         if (res < 0)
    1602            0 :                 return false;
    1603              : 
    1604            0 :         if (res > 0)
    1605              :         {
    1606              :                 /*
    1607              :                  * Timer is expired. We could drain the event manually from the
    1608              :                  * timerfd, but it's easier to simply disable it; that keeps the
    1609              :                  * platform-specific code in set_timer().
    1610              :                  */
    1611            0 :                 if (!set_timer(actx, -1))
    1612            0 :                         return false;
    1613            0 :         }
    1614              : 
    1615            0 :         if (was_expired)
    1616            0 :                 *was_expired = (res > 0);
    1617              : 
    1618            0 :         return true;
    1619            0 : }
    1620              : 
    1621              : /*
    1622              :  * Prints Curl request debugging information to stderr.
    1623              :  *
    1624              :  * Note that this will expose a number of critical secrets, so users have to opt
    1625              :  * into this (see PGOAUTHDEBUG).
    1626              :  */
    1627              : static int
    1628            0 : debug_callback(CURL *handle, curl_infotype type, char *data, size_t size,
    1629              :                            void *clientp)
    1630              : {
    1631            0 :         const char *prefix;
    1632            0 :         bool            printed_prefix = false;
    1633            0 :         PQExpBufferData buf;
    1634              : 
    1635              :         /* Prefixes are modeled off of the default libcurl debug output. */
    1636            0 :         switch (type)
    1637              :         {
    1638              :                 case CURLINFO_TEXT:
    1639            0 :                         prefix = "*";
    1640            0 :                         break;
    1641              : 
    1642              :                 case CURLINFO_HEADER_IN:        /* fall through */
    1643              :                 case CURLINFO_DATA_IN:
    1644            0 :                         prefix = "<";
    1645            0 :                         break;
    1646              : 
    1647              :                 case CURLINFO_HEADER_OUT:       /* fall through */
    1648              :                 case CURLINFO_DATA_OUT:
    1649            0 :                         prefix = ">";
    1650            0 :                         break;
    1651              : 
    1652              :                 default:
    1653            0 :                         return 0;
    1654              :         }
    1655              : 
    1656            0 :         initPQExpBuffer(&buf);
    1657              : 
    1658              :         /*
    1659              :          * Split the output into lines for readability; sometimes multiple headers
    1660              :          * are included in a single call. We also don't allow unprintable ASCII
    1661              :          * through without a basic <XX> escape.
    1662              :          */
    1663            0 :         for (int i = 0; i < size; i++)
    1664              :         {
    1665            0 :                 char            c = data[i];
    1666              : 
    1667            0 :                 if (!printed_prefix)
    1668              :                 {
    1669            0 :                         appendPQExpBuffer(&buf, "[libcurl] %s ", prefix);
    1670            0 :                         printed_prefix = true;
    1671            0 :                 }
    1672              : 
    1673            0 :                 if (c >= 0x20 && c <= 0x7E)
    1674            0 :                         appendPQExpBufferChar(&buf, c);
    1675            0 :                 else if ((type == CURLINFO_HEADER_IN
    1676            0 :                                   || type == CURLINFO_HEADER_OUT
    1677            0 :                                   || type == CURLINFO_TEXT)
    1678            0 :                                  && (c == '\r' || c == '\n'))
    1679              :                 {
    1680              :                         /*
    1681              :                          * Don't bother emitting <0D><0A> for headers and text; it's not
    1682              :                          * helpful noise.
    1683              :                          */
    1684            0 :                 }
    1685              :                 else
    1686            0 :                         appendPQExpBuffer(&buf, "<%02X>", c);
    1687              : 
    1688            0 :                 if (c == '\n')
    1689              :                 {
    1690            0 :                         appendPQExpBufferChar(&buf, c);
    1691            0 :                         printed_prefix = false;
    1692            0 :                 }
    1693            0 :         }
    1694              : 
    1695            0 :         if (printed_prefix)
    1696            0 :                 appendPQExpBufferChar(&buf, '\n');  /* finish the line */
    1697              : 
    1698            0 :         fprintf(stderr, "%s", buf.data);
    1699            0 :         termPQExpBuffer(&buf);
    1700            0 :         return 0;
    1701            0 : }
    1702              : 
    1703              : /*
    1704              :  * Initializes the two libcurl handles in the async_ctx. The multi handle,
    1705              :  * actx->curlm, is what drives the asynchronous engine and tells us what to do
    1706              :  * next. The easy handle, actx->curl, encapsulates the state for a single
    1707              :  * request/response. It's added to the multi handle as needed, during
    1708              :  * start_request().
    1709              :  */
    1710              : static bool
    1711            0 : setup_curl_handles(struct async_ctx *actx)
    1712              : {
    1713              :         /*
    1714              :          * Create our multi handle. This encapsulates the entire conversation with
    1715              :          * libcurl for this connection.
    1716              :          */
    1717            0 :         actx->curlm = curl_multi_init();
    1718            0 :         if (!actx->curlm)
    1719              :         {
    1720              :                 /* We don't get a lot of feedback on the failure reason. */
    1721            0 :                 actx_error(actx, "failed to create libcurl multi handle");
    1722            0 :                 return false;
    1723              :         }
    1724              : 
    1725              :         /*
    1726              :          * The multi handle tells us what to wait on using two callbacks. These
    1727              :          * will manipulate actx->mux as needed.
    1728              :          */
    1729            0 :         CHECK_MSETOPT(actx, CURLMOPT_SOCKETFUNCTION, register_socket, return false);
    1730            0 :         CHECK_MSETOPT(actx, CURLMOPT_SOCKETDATA, actx, return false);
    1731            0 :         CHECK_MSETOPT(actx, CURLMOPT_TIMERFUNCTION, register_timer, return false);
    1732            0 :         CHECK_MSETOPT(actx, CURLMOPT_TIMERDATA, actx, return false);
    1733              : 
    1734              :         /*
    1735              :          * Set up an easy handle. All of our requests are made serially, so we
    1736              :          * only ever need to keep track of one.
    1737              :          */
    1738            0 :         actx->curl = curl_easy_init();
    1739            0 :         if (!actx->curl)
    1740              :         {
    1741            0 :                 actx_error(actx, "failed to create libcurl handle");
    1742            0 :                 return false;
    1743              :         }
    1744              : 
    1745              :         /*
    1746              :          * Multi-threaded applications must set CURLOPT_NOSIGNAL. This requires us
    1747              :          * to handle the possibility of SIGPIPE ourselves using pq_block_sigpipe;
    1748              :          * see pg_fe_run_oauth_flow().
    1749              :          *
    1750              :          * NB: If libcurl is not built against a friendly DNS resolver (c-ares or
    1751              :          * threaded), setting this option prevents DNS lookups from timing out
    1752              :          * correctly. We warn about this situation at configure time.
    1753              :          *
    1754              :          * TODO: Perhaps there's a clever way to warn the user about synchronous
    1755              :          * DNS at runtime too? It's not immediately clear how to do that in a
    1756              :          * helpful way: for many standard single-threaded use cases, the user
    1757              :          * might not care at all, so spraying warnings to stderr would probably do
    1758              :          * more harm than good.
    1759              :          */
    1760            0 :         CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L, return false);
    1761              : 
    1762            0 :         if (actx->debugging)
    1763              :         {
    1764              :                 /*
    1765              :                  * Set a callback for retrieving error information from libcurl, the
    1766              :                  * function only takes effect when CURLOPT_VERBOSE has been set so
    1767              :                  * make sure the order is kept.
    1768              :                  */
    1769            0 :                 CHECK_SETOPT(actx, CURLOPT_DEBUGFUNCTION, debug_callback, return false);
    1770            0 :                 CHECK_SETOPT(actx, CURLOPT_VERBOSE, 1L, return false);
    1771            0 :         }
    1772              : 
    1773            0 :         CHECK_SETOPT(actx, CURLOPT_ERRORBUFFER, actx->curl_err, return false);
    1774              : 
    1775              :         /*
    1776              :          * Only HTTPS is allowed. (Debug mode additionally allows HTTP; this is
    1777              :          * intended for testing only.)
    1778              :          *
    1779              :          * There's a bit of unfortunate complexity around the choice of
    1780              :          * CURLoption. CURLOPT_PROTOCOLS is deprecated in modern Curls, but its
    1781              :          * replacement didn't show up until relatively recently.
    1782              :          */
    1783              :         {
    1784              : #if CURL_AT_LEAST_VERSION(7, 85, 0)
    1785            0 :                 const CURLoption popt = CURLOPT_PROTOCOLS_STR;
    1786            0 :                 const char *protos = "https";
    1787            0 :                 const char *const unsafe = "https,http";
    1788              : #else
    1789              :                 const CURLoption popt = CURLOPT_PROTOCOLS;
    1790              :                 long            protos = CURLPROTO_HTTPS;
    1791              :                 const long      unsafe = CURLPROTO_HTTPS | CURLPROTO_HTTP;
    1792              : #endif
    1793              : 
    1794            0 :                 if (actx->debugging)
    1795            0 :                         protos = unsafe;
    1796              : 
    1797            0 :                 CHECK_SETOPT(actx, popt, protos, return false);
    1798            0 :         }
    1799              : 
    1800              :         /*
    1801              :          * If we're in debug mode, allow the developer to change the trusted CA
    1802              :          * list. For now, this is not something we expose outside of the UNSAFE
    1803              :          * mode, because it's not clear that it's useful in production: both libpq
    1804              :          * and the user's browser must trust the same authorization servers for
    1805              :          * the flow to work at all, so any changes to the roots are likely to be
    1806              :          * done system-wide.
    1807              :          */
    1808            0 :         if (actx->debugging)
    1809              :         {
    1810            0 :                 const char *env;
    1811              : 
    1812            0 :                 if ((env = getenv("PGOAUTHCAFILE")) != NULL)
    1813            0 :                         CHECK_SETOPT(actx, CURLOPT_CAINFO, env, return false);
    1814            0 :         }
    1815              : 
    1816              :         /*
    1817              :          * Suppress the Accept header to make our request as minimal as possible.
    1818              :          * (Ideally we would set it to "application/json" instead, but OpenID is
    1819              :          * pretty strict when it comes to provider behavior, so we have to check
    1820              :          * what comes back anyway.)
    1821              :          */
    1822            0 :         actx->headers = curl_slist_append(actx->headers, "Accept:");
    1823            0 :         if (actx->headers == NULL)
    1824              :         {
    1825            0 :                 actx_error(actx, "out of memory");
    1826            0 :                 return false;
    1827              :         }
    1828            0 :         CHECK_SETOPT(actx, CURLOPT_HTTPHEADER, actx->headers, return false);
    1829              : 
    1830            0 :         return true;
    1831            0 : }
    1832              : 
    1833              : /*
    1834              :  * Generic HTTP Request Handlers
    1835              :  */
    1836              : 
    1837              : /*
    1838              :  * Response callback from libcurl which appends the response body into
    1839              :  * actx->work_data (see start_request()). The maximum size of the data is
    1840              :  * defined by CURL_MAX_WRITE_SIZE which by default is 16kb (and can only be
    1841              :  * changed by recompiling libcurl).
    1842              :  */
    1843              : static size_t
    1844            0 : append_data(char *buf, size_t size, size_t nmemb, void *userdata)
    1845              : {
    1846            0 :         struct async_ctx *actx = userdata;
    1847            0 :         PQExpBuffer resp = &actx->work_data;
    1848            0 :         size_t          len = size * nmemb;
    1849              : 
    1850              :         /* In case we receive data over the threshold, abort the transfer */
    1851            0 :         if ((resp->len + len) > MAX_OAUTH_RESPONSE_SIZE)
    1852              :         {
    1853            0 :                 actx_error(actx, "response is too large");
    1854            0 :                 return 0;
    1855              :         }
    1856              : 
    1857              :         /* The data passed from libcurl is not null-terminated */
    1858            0 :         appendBinaryPQExpBuffer(resp, buf, len);
    1859              : 
    1860              :         /*
    1861              :          * Signal an error in order to abort the transfer in case we ran out of
    1862              :          * memory in accepting the data.
    1863              :          */
    1864            0 :         if (PQExpBufferBroken(resp))
    1865              :         {
    1866            0 :                 actx_error(actx, "out of memory");
    1867            0 :                 return 0;
    1868              :         }
    1869              : 
    1870            0 :         return len;
    1871            0 : }
    1872              : 
    1873              : /*
    1874              :  * Begins an HTTP request on the multi handle. The caller should have set up all
    1875              :  * request-specific options on actx->curl first. The server's response body will
    1876              :  * be accumulated in actx->work_data (which will be reset, so don't store
    1877              :  * anything important there across this call).
    1878              :  *
    1879              :  * Once a request is queued, it can be driven to completion via drive_request().
    1880              :  * If actx->running is zero upon return, the request has already finished and
    1881              :  * drive_request() can be called without returning control to the client.
    1882              :  */
    1883              : static bool
    1884            0 : start_request(struct async_ctx *actx)
    1885              : {
    1886            0 :         CURLMcode       err;
    1887              : 
    1888            0 :         resetPQExpBuffer(&actx->work_data);
    1889            0 :         CHECK_SETOPT(actx, CURLOPT_WRITEFUNCTION, append_data, return false);
    1890            0 :         CHECK_SETOPT(actx, CURLOPT_WRITEDATA, actx, return false);
    1891              : 
    1892            0 :         err = curl_multi_add_handle(actx->curlm, actx->curl);
    1893            0 :         if (err)
    1894              :         {
    1895            0 :                 actx_error(actx, "failed to queue HTTP request: %s",
    1896              :                                    curl_multi_strerror(err));
    1897            0 :                 return false;
    1898              :         }
    1899              : 
    1900              :         /*
    1901              :          * actx->running tracks the number of running handles, so we can
    1902              :          * immediately call back if no waiting is needed.
    1903              :          *
    1904              :          * Even though this is nominally an asynchronous process, there are some
    1905              :          * operations that can synchronously fail by this point (e.g. connections
    1906              :          * to closed local ports) or even synchronously succeed if the stars align
    1907              :          * (all the libcurl connection caches hit and the server is fast).
    1908              :          */
    1909            0 :         err = curl_multi_socket_action(actx->curlm, CURL_SOCKET_TIMEOUT, 0, &actx->running);
    1910            0 :         if (err)
    1911              :         {
    1912            0 :                 actx_error(actx, "asynchronous HTTP request failed: %s",
    1913              :                                    curl_multi_strerror(err));
    1914            0 :                 return false;
    1915              :         }
    1916              : 
    1917            0 :         return true;
    1918            0 : }
    1919              : 
    1920              : /*
    1921              :  * CURL_IGNORE_DEPRECATION was added in 7.87.0. If it's not defined, we can make
    1922              :  * it a no-op.
    1923              :  */
    1924              : #ifndef CURL_IGNORE_DEPRECATION
    1925              : #define CURL_IGNORE_DEPRECATION(x) x
    1926              : #endif
    1927              : 
    1928              : /*
    1929              :  * Add another macro layer that inserts the needed semicolon, to avoid having
    1930              :  * to write a literal semicolon in the call below, which would break pgindent.
    1931              :  */
    1932              : #define PG_CURL_IGNORE_DEPRECATION(x) CURL_IGNORE_DEPRECATION(x;)
    1933              : 
    1934              : /*
    1935              :  * Drives the multi handle towards completion. The caller should have already
    1936              :  * set up an asynchronous request via start_request().
    1937              :  */
    1938              : static PostgresPollingStatusType
    1939            0 : drive_request(struct async_ctx *actx)
    1940              : {
    1941            0 :         CURLMcode       err;
    1942            0 :         CURLMsg    *msg;
    1943            0 :         int                     msgs_left;
    1944            0 :         bool            done;
    1945              : 
    1946            0 :         if (actx->running)
    1947              :         {
    1948              :                 /*---
    1949              :                  * There's an async request in progress. Pump the multi handle.
    1950              :                  *
    1951              :                  * curl_multi_socket_all() is officially deprecated, because it's
    1952              :                  * inefficient and pointless if your event loop has already handed you
    1953              :                  * the exact sockets that are ready. But that's not our use case --
    1954              :                  * our client has no way to tell us which sockets are ready. (They
    1955              :                  * don't even know there are sockets to begin with.)
    1956              :                  *
    1957              :                  * We can grab the list of triggered events from the multiplexer
    1958              :                  * ourselves, but that's effectively what curl_multi_socket_all() is
    1959              :                  * going to do. And there are currently no plans for the Curl project
    1960              :                  * to remove or break this API, so ignore the deprecation. See
    1961              :                  *
    1962              :                  *    https://curl.se/mail/lib-2024-11/0028.html
    1963              :                  */
    1964            0 :                 PG_CURL_IGNORE_DEPRECATION(err =
    1965              :                                                                    curl_multi_socket_all(actx->curlm,
    1966              :                                                                                                                  &actx->running));
    1967              : 
    1968            0 :                 if (err)
    1969              :                 {
    1970            0 :                         actx_error(actx, "asynchronous HTTP request failed: %s",
    1971              :                                            curl_multi_strerror(err));
    1972            0 :                         return PGRES_POLLING_FAILED;
    1973              :                 }
    1974              : 
    1975            0 :                 if (actx->running)
    1976              :                 {
    1977              :                         /* We'll come back again. */
    1978            0 :                         return PGRES_POLLING_READING;
    1979              :                 }
    1980            0 :         }
    1981              : 
    1982            0 :         done = false;
    1983            0 :         while ((msg = curl_multi_info_read(actx->curlm, &msgs_left)) != NULL)
    1984              :         {
    1985            0 :                 if (msg->msg != CURLMSG_DONE)
    1986              :                 {
    1987              :                         /*
    1988              :                          * Future libcurl versions may define new message types; we don't
    1989              :                          * know how to handle them, so we'll ignore them.
    1990              :                          */
    1991            0 :                         continue;
    1992              :                 }
    1993              : 
    1994              :                 /* First check the status of the request itself. */
    1995            0 :                 if (msg->data.result != CURLE_OK)
    1996              :                 {
    1997              :                         /*
    1998              :                          * If a more specific error hasn't already been reported, use
    1999              :                          * libcurl's description.
    2000              :                          */
    2001            0 :                         if (actx->errbuf.len == 0)
    2002            0 :                                 actx_error_str(actx, curl_easy_strerror(msg->data.result));
    2003              : 
    2004            0 :                         return PGRES_POLLING_FAILED;
    2005              :                 }
    2006              : 
    2007              :                 /* Now remove the finished handle; we'll add it back later if needed. */
    2008            0 :                 err = curl_multi_remove_handle(actx->curlm, msg->easy_handle);
    2009            0 :                 if (err)
    2010              :                 {
    2011            0 :                         actx_error(actx, "libcurl easy handle removal failed: %s",
    2012              :                                            curl_multi_strerror(err));
    2013            0 :                         return PGRES_POLLING_FAILED;
    2014              :                 }
    2015              : 
    2016            0 :                 done = true;
    2017              :         }
    2018              : 
    2019              :         /* Sanity check. */
    2020            0 :         if (!done)
    2021              :         {
    2022            0 :                 actx_error(actx, "no result was retrieved for the finished handle");
    2023            0 :                 return PGRES_POLLING_FAILED;
    2024              :         }
    2025              : 
    2026            0 :         return PGRES_POLLING_OK;
    2027            0 : }
    2028              : 
    2029              : /*
    2030              :  * URL-Encoding Helpers
    2031              :  */
    2032              : 
    2033              : /*
    2034              :  * Encodes a string using the application/x-www-form-urlencoded format, and
    2035              :  * appends it to the given buffer.
    2036              :  */
    2037              : static void
    2038            0 : append_urlencoded(PQExpBuffer buf, const char *s)
    2039              : {
    2040            0 :         char       *escaped;
    2041            0 :         char       *haystack;
    2042            0 :         char       *match;
    2043              : 
    2044              :         /* The first parameter to curl_easy_escape is deprecated by Curl */
    2045            0 :         escaped = curl_easy_escape(NULL, s, 0);
    2046            0 :         if (!escaped)
    2047              :         {
    2048            0 :                 termPQExpBuffer(buf);   /* mark the buffer broken */
    2049            0 :                 return;
    2050              :         }
    2051              : 
    2052              :         /*
    2053              :          * curl_easy_escape() almost does what we want, but we need the
    2054              :          * query-specific flavor which uses '+' instead of '%20' for spaces. The
    2055              :          * Curl command-line tool does this with a simple search-and-replace, so
    2056              :          * follow its lead.
    2057              :          */
    2058            0 :         haystack = escaped;
    2059              : 
    2060            0 :         while ((match = strstr(haystack, "%20")) != NULL)
    2061              :         {
    2062              :                 /* Append the unmatched portion, followed by the plus sign. */
    2063            0 :                 appendBinaryPQExpBuffer(buf, haystack, match - haystack);
    2064            0 :                 appendPQExpBufferChar(buf, '+');
    2065              : 
    2066              :                 /* Keep searching after the match. */
    2067            0 :                 haystack = match + 3 /* strlen("%20") */ ;
    2068              :         }
    2069              : 
    2070              :         /* Push the remainder of the string onto the buffer. */
    2071            0 :         appendPQExpBufferStr(buf, haystack);
    2072              : 
    2073            0 :         curl_free(escaped);
    2074            0 : }
    2075              : 
    2076              : /*
    2077              :  * Convenience wrapper for encoding a single string. Returns NULL on allocation
    2078              :  * failure.
    2079              :  */
    2080              : static char *
    2081            0 : urlencode(const char *s)
    2082              : {
    2083            0 :         PQExpBufferData buf;
    2084              : 
    2085            0 :         initPQExpBuffer(&buf);
    2086            0 :         append_urlencoded(&buf, s);
    2087              : 
    2088            0 :         return PQExpBufferDataBroken(buf) ? NULL : buf.data;
    2089            0 : }
    2090              : 
    2091              : /*
    2092              :  * Appends a key/value pair to the end of an application/x-www-form-urlencoded
    2093              :  * list.
    2094              :  */
    2095              : static void
    2096            0 : build_urlencoded(PQExpBuffer buf, const char *key, const char *value)
    2097              : {
    2098            0 :         if (buf->len)
    2099            0 :                 appendPQExpBufferChar(buf, '&');
    2100              : 
    2101            0 :         append_urlencoded(buf, key);
    2102            0 :         appendPQExpBufferChar(buf, '=');
    2103            0 :         append_urlencoded(buf, value);
    2104            0 : }
    2105              : 
    2106              : /*
    2107              :  * Specific HTTP Request Handlers
    2108              :  *
    2109              :  * This is finally the beginning of the actual application logic. Generally
    2110              :  * speaking, a single request consists of a start_* and a finish_* step, with
    2111              :  * drive_request() pumping the machine in between.
    2112              :  */
    2113              : 
    2114              : /*
    2115              :  * Queue an OpenID Provider Configuration Request:
    2116              :  *
    2117              :  *     https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
    2118              :  *     https://www.rfc-editor.org/rfc/rfc8414#section-3.1
    2119              :  *
    2120              :  * This is done first to get the endpoint URIs we need to contact and to make
    2121              :  * sure the provider provides a device authorization flow. finish_discovery()
    2122              :  * will fill in actx->provider.
    2123              :  */
    2124              : static bool
    2125            0 : start_discovery(struct async_ctx *actx, const char *discovery_uri)
    2126              : {
    2127            0 :         CHECK_SETOPT(actx, CURLOPT_HTTPGET, 1L, return false);
    2128            0 :         CHECK_SETOPT(actx, CURLOPT_URL, discovery_uri, return false);
    2129              : 
    2130            0 :         return start_request(actx);
    2131            0 : }
    2132              : 
    2133              : static bool
    2134            0 : finish_discovery(struct async_ctx *actx)
    2135              : {
    2136            0 :         long            response_code;
    2137              : 
    2138              :         /*----
    2139              :          * Now check the response. OIDC Discovery 1.0 is pretty strict:
    2140              :          *
    2141              :          *     A successful response MUST use the 200 OK HTTP status code and
    2142              :          *     return a JSON object using the application/json content type that
    2143              :          *     contains a set of Claims as its members that are a subset of the
    2144              :          *     Metadata values defined in Section 3.
    2145              :          *
    2146              :          * Compared to standard HTTP semantics, this makes life easy -- we don't
    2147              :          * need to worry about redirections (which would call the Issuer host
    2148              :          * validation into question), or non-authoritative responses, or any other
    2149              :          * complications.
    2150              :          */
    2151            0 :         CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
    2152              : 
    2153            0 :         if (response_code != 200)
    2154              :         {
    2155            0 :                 actx_error(actx, "unexpected response code %ld", response_code);
    2156            0 :                 return false;
    2157              :         }
    2158              : 
    2159              :         /*
    2160              :          * Pull the fields we care about from the document.
    2161              :          */
    2162            0 :         actx->errctx = libpq_gettext("failed to parse OpenID discovery document");
    2163            0 :         if (!parse_provider(actx, &actx->provider))
    2164            0 :                 return false;                   /* error message already set */
    2165              : 
    2166              :         /*
    2167              :          * Fill in any defaults for OPTIONAL/RECOMMENDED fields we care about.
    2168              :          */
    2169            0 :         if (!actx->provider.grant_types_supported)
    2170              :         {
    2171              :                 /*
    2172              :                  * Per Section 3, the default is ["authorization_code", "implicit"].
    2173              :                  */
    2174            0 :                 struct curl_slist *temp = actx->provider.grant_types_supported;
    2175              : 
    2176            0 :                 temp = curl_slist_append(temp, "authorization_code");
    2177            0 :                 if (temp)
    2178              :                 {
    2179            0 :                         temp = curl_slist_append(temp, "implicit");
    2180            0 :                 }
    2181              : 
    2182            0 :                 if (!temp)
    2183              :                 {
    2184            0 :                         actx_error(actx, "out of memory");
    2185            0 :                         return false;
    2186              :                 }
    2187              : 
    2188            0 :                 actx->provider.grant_types_supported = temp;
    2189            0 :         }
    2190              : 
    2191            0 :         return true;
    2192            0 : }
    2193              : 
    2194              : /*
    2195              :  * Ensure that the discovery document is provided by the expected issuer.
    2196              :  * Currently, issuers are statically configured in the connection string.
    2197              :  */
    2198              : static bool
    2199            0 : check_issuer(struct async_ctx *actx, PGconn *conn)
    2200              : {
    2201            0 :         const struct provider *provider = &actx->provider;
    2202            0 :         const char *oauth_issuer_id = conn_oauth_issuer_id(conn);
    2203              : 
    2204            0 :         Assert(oauth_issuer_id);        /* ensured by setup_oauth_parameters() */
    2205            0 :         Assert(provider->issuer);    /* ensured by parse_provider() */
    2206              : 
    2207              :         /*---
    2208              :          * We require strict equality for issuer identifiers -- no path or case
    2209              :          * normalization, no substitution of default ports and schemes, etc. This
    2210              :          * is done to match the rules in OIDC Discovery Sec. 4.3 for config
    2211              :          * validation:
    2212              :          *
    2213              :          *    The issuer value returned MUST be identical to the Issuer URL that
    2214              :          *    was used as the prefix to /.well-known/openid-configuration to
    2215              :          *    retrieve the configuration information.
    2216              :          *
    2217              :          * as well as the rules set out in RFC 9207 for avoiding mix-up attacks:
    2218              :          *
    2219              :          *    Clients MUST then [...] compare the result to the issuer identifier
    2220              :          *    of the authorization server where the authorization request was
    2221              :          *    sent to. This comparison MUST use simple string comparison as defined
    2222              :          *    in Section 6.2.1 of [RFC3986].
    2223              :          */
    2224            0 :         if (strcmp(oauth_issuer_id, provider->issuer) != 0)
    2225              :         {
    2226            0 :                 actx_error(actx,
    2227              :                                    "the issuer identifier (%s) does not match oauth_issuer (%s)",
    2228              :                                    provider->issuer, oauth_issuer_id);
    2229            0 :                 return false;
    2230              :         }
    2231              : 
    2232            0 :         return true;
    2233            0 : }
    2234              : 
    2235              : #define HTTPS_SCHEME "https://"
    2236              : #define OAUTH_GRANT_TYPE_DEVICE_CODE "urn:ietf:params:oauth:grant-type:device_code"
    2237              : 
    2238              : /*
    2239              :  * Ensure that the provider supports the Device Authorization flow (i.e. it
    2240              :  * provides an authorization endpoint, and both the token and authorization
    2241              :  * endpoint URLs seem reasonable).
    2242              :  */
    2243              : static bool
    2244            0 : check_for_device_flow(struct async_ctx *actx)
    2245              : {
    2246            0 :         const struct provider *provider = &actx->provider;
    2247              : 
    2248            0 :         Assert(provider->issuer);    /* ensured by parse_provider() */
    2249            0 :         Assert(provider->token_endpoint);    /* ensured by parse_provider() */
    2250              : 
    2251            0 :         if (!provider->device_authorization_endpoint)
    2252              :         {
    2253            0 :                 actx_error(actx,
    2254              :                                    "issuer \"%s\" does not provide a device authorization endpoint",
    2255              :                                    provider->issuer);
    2256            0 :                 return false;
    2257              :         }
    2258              : 
    2259              :         /*
    2260              :          * The original implementation checked that OAUTH_GRANT_TYPE_DEVICE_CODE
    2261              :          * was present in the discovery document's grant_types_supported list. MS
    2262              :          * Entra does not advertise this grant type, though, and since it doesn't
    2263              :          * make sense to stand up a device_authorization_endpoint without also
    2264              :          * accepting device codes at the token_endpoint, that's the only thing we
    2265              :          * currently require.
    2266              :          */
    2267              : 
    2268              :         /*
    2269              :          * Although libcurl will fail later if the URL contains an unsupported
    2270              :          * scheme, that error message is going to be a bit opaque. This is a
    2271              :          * decent time to bail out if we're not using HTTPS for the endpoints
    2272              :          * we'll use for the flow.
    2273              :          */
    2274            0 :         if (!actx->debugging)
    2275              :         {
    2276            0 :                 if (pg_strncasecmp(provider->device_authorization_endpoint,
    2277            0 :                                                    HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
    2278              :                 {
    2279            0 :                         actx_error(actx,
    2280              :                                            "device authorization endpoint \"%s\" must use HTTPS",
    2281              :                                            provider->device_authorization_endpoint);
    2282            0 :                         return false;
    2283              :                 }
    2284              : 
    2285            0 :                 if (pg_strncasecmp(provider->token_endpoint,
    2286            0 :                                                    HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
    2287              :                 {
    2288            0 :                         actx_error(actx,
    2289              :                                            "token endpoint \"%s\" must use HTTPS",
    2290              :                                            provider->token_endpoint);
    2291            0 :                         return false;
    2292              :                 }
    2293            0 :         }
    2294              : 
    2295            0 :         return true;
    2296            0 : }
    2297              : 
    2298              : /*
    2299              :  * Adds the client ID (and secret, if provided) to the current request, using
    2300              :  * either HTTP headers or the request body.
    2301              :  */
    2302              : static bool
    2303            0 : add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
    2304              : {
    2305            0 :         const char *oauth_client_id = conn_oauth_client_id(conn);
    2306            0 :         const char *oauth_client_secret = conn_oauth_client_secret(conn);
    2307              : 
    2308            0 :         bool            success = false;
    2309            0 :         char       *username = NULL;
    2310            0 :         char       *password = NULL;
    2311              : 
    2312            0 :         if (oauth_client_secret)        /* Zero-length secrets are permitted! */
    2313              :         {
    2314              :                 /*----
    2315              :                  * Use HTTP Basic auth to send the client_id and secret. Per RFC 6749,
    2316              :                  * Sec. 2.3.1,
    2317              :                  *
    2318              :                  *   Including the client credentials in the request-body using the
    2319              :                  *   two parameters is NOT RECOMMENDED and SHOULD be limited to
    2320              :                  *   clients unable to directly utilize the HTTP Basic authentication
    2321              :                  *   scheme (or other password-based HTTP authentication schemes).
    2322              :                  *
    2323              :                  * Additionally:
    2324              :                  *
    2325              :                  *   The client identifier is encoded using the
    2326              :                  *   "application/x-www-form-urlencoded" encoding algorithm per Appendix
    2327              :                  *   B, and the encoded value is used as the username; the client
    2328              :                  *   password is encoded using the same algorithm and used as the
    2329              :                  *   password.
    2330              :                  *
    2331              :                  * (Appendix B modifies application/x-www-form-urlencoded by requiring
    2332              :                  * an initial UTF-8 encoding step. Since the client ID and secret must
    2333              :                  * both be 7-bit ASCII -- RFC 6749 Appendix A -- we don't worry about
    2334              :                  * that in this function.)
    2335              :                  *
    2336              :                  * client_id is not added to the request body in this case. Not only
    2337              :                  * would it be redundant, but some providers in the wild (e.g. Okta)
    2338              :                  * refuse to accept it.
    2339              :                  */
    2340            0 :                 username = urlencode(oauth_client_id);
    2341            0 :                 password = urlencode(oauth_client_secret);
    2342              : 
    2343            0 :                 if (!username || !password)
    2344              :                 {
    2345            0 :                         actx_error(actx, "out of memory");
    2346            0 :                         goto cleanup;
    2347              :                 }
    2348              : 
    2349            0 :                 CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_BASIC, goto cleanup);
    2350            0 :                 CHECK_SETOPT(actx, CURLOPT_USERNAME, username, goto cleanup);
    2351            0 :                 CHECK_SETOPT(actx, CURLOPT_PASSWORD, password, goto cleanup);
    2352              : 
    2353            0 :                 actx->used_basic_auth = true;
    2354            0 :         }
    2355              :         else
    2356              :         {
    2357              :                 /*
    2358              :                  * If we're not otherwise authenticating, client_id is REQUIRED in the
    2359              :                  * request body.
    2360              :                  */
    2361            0 :                 build_urlencoded(reqbody, "client_id", oauth_client_id);
    2362              : 
    2363            0 :                 CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, goto cleanup);
    2364            0 :                 actx->used_basic_auth = false;
    2365              :         }
    2366              : 
    2367            0 :         success = true;
    2368              : 
    2369              : cleanup:
    2370            0 :         free(username);
    2371            0 :         free(password);
    2372              : 
    2373            0 :         return success;
    2374            0 : }
    2375              : 
    2376              : /*
    2377              :  * Queue a Device Authorization Request:
    2378              :  *
    2379              :  *     https://www.rfc-editor.org/rfc/rfc8628#section-3.1
    2380              :  *
    2381              :  * This is the second step. We ask the provider to verify the end user out of
    2382              :  * band and authorize us to act on their behalf; it will give us the required
    2383              :  * nonces for us to later poll the request status, which we'll grab in
    2384              :  * finish_device_authz().
    2385              :  */
    2386              : static bool
    2387            0 : start_device_authz(struct async_ctx *actx, PGconn *conn)
    2388              : {
    2389            0 :         const char *oauth_scope = conn_oauth_scope(conn);
    2390            0 :         const char *device_authz_uri = actx->provider.device_authorization_endpoint;
    2391            0 :         PQExpBuffer work_buffer = &actx->work_data;
    2392              : 
    2393            0 :         Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
    2394            0 :         Assert(device_authz_uri);       /* ensured by check_for_device_flow() */
    2395              : 
    2396              :         /* Construct our request body. */
    2397            0 :         resetPQExpBuffer(work_buffer);
    2398            0 :         if (oauth_scope && oauth_scope[0])
    2399            0 :                 build_urlencoded(work_buffer, "scope", oauth_scope);
    2400              : 
    2401            0 :         if (!add_client_identification(actx, work_buffer, conn))
    2402            0 :                 return false;
    2403              : 
    2404            0 :         if (PQExpBufferBroken(work_buffer))
    2405              :         {
    2406            0 :                 actx_error(actx, "out of memory");
    2407            0 :                 return false;
    2408              :         }
    2409              : 
    2410              :         /* Make our request. */
    2411            0 :         CHECK_SETOPT(actx, CURLOPT_URL, device_authz_uri, return false);
    2412            0 :         CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
    2413              : 
    2414            0 :         return start_request(actx);
    2415            0 : }
    2416              : 
    2417              : static bool
    2418            0 : finish_device_authz(struct async_ctx *actx)
    2419              : {
    2420            0 :         long            response_code;
    2421              : 
    2422            0 :         CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
    2423              : 
    2424              :         /*
    2425              :          * Per RFC 8628, Section 3, a successful device authorization response
    2426              :          * uses 200 OK.
    2427              :          */
    2428            0 :         if (response_code == 200)
    2429              :         {
    2430            0 :                 actx->errctx = libpq_gettext("failed to parse device authorization");
    2431            0 :                 if (!parse_device_authz(actx, &actx->authz))
    2432            0 :                         return false;           /* error message already set */
    2433              : 
    2434            0 :                 return true;
    2435              :         }
    2436              : 
    2437              :         /*
    2438              :          * The device authorization endpoint uses the same error response as the
    2439              :          * token endpoint, so the error handling roughly follows
    2440              :          * finish_token_request(). The key difference is that an error here is
    2441              :          * immediately fatal.
    2442              :          */
    2443            0 :         if (response_code == 400 || response_code == 401)
    2444              :         {
    2445            0 :                 struct token_error err = {0};
    2446              : 
    2447            0 :                 if (!parse_token_error(actx, &err))
    2448              :                 {
    2449            0 :                         free_token_error(&err);
    2450            0 :                         return false;
    2451              :                 }
    2452              : 
    2453              :                 /* Copy the token error into the context error buffer */
    2454            0 :                 record_token_error(actx, &err);
    2455              : 
    2456            0 :                 free_token_error(&err);
    2457            0 :                 return false;
    2458            0 :         }
    2459              : 
    2460              :         /* Any other response codes are considered invalid */
    2461            0 :         actx_error(actx, "unexpected response code %ld", response_code);
    2462            0 :         return false;
    2463            0 : }
    2464              : 
    2465              : /*
    2466              :  * Queue an Access Token Request:
    2467              :  *
    2468              :  *     https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3
    2469              :  *
    2470              :  * This is the final step. We continually poll the token endpoint to see if the
    2471              :  * user has authorized us yet. finish_token_request() will pull either the token
    2472              :  * or a (ideally temporary) error status from the provider.
    2473              :  */
    2474              : static bool
    2475            0 : start_token_request(struct async_ctx *actx, PGconn *conn)
    2476              : {
    2477            0 :         const char *token_uri = actx->provider.token_endpoint;
    2478            0 :         const char *device_code = actx->authz.device_code;
    2479            0 :         PQExpBuffer work_buffer = &actx->work_data;
    2480              : 
    2481            0 :         Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
    2482            0 :         Assert(token_uri);                      /* ensured by parse_provider() */
    2483            0 :         Assert(device_code);            /* ensured by parse_device_authz() */
    2484              : 
    2485              :         /* Construct our request body. */
    2486            0 :         resetPQExpBuffer(work_buffer);
    2487            0 :         build_urlencoded(work_buffer, "device_code", device_code);
    2488            0 :         build_urlencoded(work_buffer, "grant_type", OAUTH_GRANT_TYPE_DEVICE_CODE);
    2489              : 
    2490            0 :         if (!add_client_identification(actx, work_buffer, conn))
    2491            0 :                 return false;
    2492              : 
    2493            0 :         if (PQExpBufferBroken(work_buffer))
    2494              :         {
    2495            0 :                 actx_error(actx, "out of memory");
    2496            0 :                 return false;
    2497              :         }
    2498              : 
    2499              :         /* Make our request. */
    2500            0 :         CHECK_SETOPT(actx, CURLOPT_URL, token_uri, return false);
    2501            0 :         CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
    2502              : 
    2503            0 :         return start_request(actx);
    2504            0 : }
    2505              : 
    2506              : static bool
    2507            0 : finish_token_request(struct async_ctx *actx, struct token *tok)
    2508              : {
    2509            0 :         long            response_code;
    2510              : 
    2511            0 :         CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
    2512              : 
    2513              :         /*
    2514              :          * Per RFC 6749, Section 5, a successful response uses 200 OK.
    2515              :          */
    2516            0 :         if (response_code == 200)
    2517              :         {
    2518            0 :                 actx->errctx = libpq_gettext("failed to parse access token response");
    2519            0 :                 if (!parse_access_token(actx, tok))
    2520            0 :                         return false;           /* error message already set */
    2521              : 
    2522            0 :                 return true;
    2523              :         }
    2524              : 
    2525              :         /*
    2526              :          * An error response uses either 400 Bad Request or 401 Unauthorized.
    2527              :          * There are references online to implementations using 403 for error
    2528              :          * return which would violate the specification. For now we stick to the
    2529              :          * specification but we might have to revisit this.
    2530              :          */
    2531            0 :         if (response_code == 400 || response_code == 401)
    2532              :         {
    2533            0 :                 if (!parse_token_error(actx, &tok->err))
    2534            0 :                         return false;
    2535              : 
    2536            0 :                 return true;
    2537              :         }
    2538              : 
    2539              :         /* Any other response codes are considered invalid */
    2540            0 :         actx_error(actx, "unexpected response code %ld", response_code);
    2541            0 :         return false;
    2542            0 : }
    2543              : 
    2544              : /*
    2545              :  * Finishes the token request and examines the response. If the flow has
    2546              :  * completed, a valid token will be returned via the parameter list. Otherwise,
    2547              :  * the token parameter remains unchanged, and the caller needs to wait for
    2548              :  * another interval (which will have been increased in response to a slow_down
    2549              :  * message from the server) before starting a new token request.
    2550              :  *
    2551              :  * False is returned only for permanent error conditions.
    2552              :  */
    2553              : static bool
    2554            0 : handle_token_response(struct async_ctx *actx, char **token)
    2555              : {
    2556            0 :         bool            success = false;
    2557            0 :         struct token tok = {0};
    2558            0 :         const struct token_error *err;
    2559              : 
    2560            0 :         if (!finish_token_request(actx, &tok))
    2561            0 :                 goto token_cleanup;
    2562              : 
    2563              :         /* A successful token request gives either a token or an in-band error. */
    2564            0 :         Assert(tok.access_token || tok.err.error);
    2565              : 
    2566            0 :         if (tok.access_token)
    2567              :         {
    2568            0 :                 *token = tok.access_token;
    2569            0 :                 tok.access_token = NULL;
    2570              : 
    2571            0 :                 success = true;
    2572            0 :                 goto token_cleanup;
    2573              :         }
    2574              : 
    2575              :         /*
    2576              :          * authorization_pending and slow_down are the only acceptable errors;
    2577              :          * anything else and we bail. These are defined in RFC 8628, Sec. 3.5.
    2578              :          */
    2579            0 :         err = &tok.err;
    2580            0 :         if (strcmp(err->error, "authorization_pending") != 0 &&
    2581            0 :                 strcmp(err->error, "slow_down") != 0)
    2582              :         {
    2583            0 :                 record_token_error(actx, err);
    2584            0 :                 goto token_cleanup;
    2585              :         }
    2586              : 
    2587              :         /*
    2588              :          * A slow_down error requires us to permanently increase our retry
    2589              :          * interval by five seconds.
    2590              :          */
    2591            0 :         if (strcmp(err->error, "slow_down") == 0)
    2592              :         {
    2593            0 :                 int                     prev_interval = actx->authz.interval;
    2594              : 
    2595            0 :                 actx->authz.interval += 5;
    2596            0 :                 if (actx->authz.interval < prev_interval)
    2597              :                 {
    2598            0 :                         actx_error(actx, "slow_down interval overflow");
    2599            0 :                         goto token_cleanup;
    2600              :                 }
    2601            0 :         }
    2602              : 
    2603            0 :         success = true;
    2604              : 
    2605              : token_cleanup:
    2606            0 :         free_token(&tok);
    2607            0 :         return success;
    2608            0 : }
    2609              : 
    2610              : /*
    2611              :  * Displays a device authorization prompt for action by the end user, either via
    2612              :  * the PQauthDataHook, or by a message on standard error if no hook is set.
    2613              :  */
    2614              : static bool
    2615            0 : prompt_user(struct async_ctx *actx, PGconn *conn)
    2616              : {
    2617            0 :         int                     res;
    2618            0 :         PGpromptOAuthDevice prompt = {
    2619            0 :                 .verification_uri = actx->authz.verification_uri,
    2620            0 :                 .user_code = actx->authz.user_code,
    2621            0 :                 .verification_uri_complete = actx->authz.verification_uri_complete,
    2622            0 :                 .expires_in = actx->authz.expires_in,
    2623              :         };
    2624            0 :         PQauthDataHook_type hook = PQgetAuthDataHook();
    2625              : 
    2626            0 :         res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
    2627              : 
    2628            0 :         if (!res)
    2629              :         {
    2630              :                 /*
    2631              :                  * translator: The first %s is a URL for the user to visit in a
    2632              :                  * browser, and the second %s is a code to be copy-pasted there.
    2633              :                  */
    2634            0 :                 fprintf(stderr, libpq_gettext("Visit %s and enter the code: %s\n"),
    2635            0 :                                 prompt.verification_uri, prompt.user_code);
    2636            0 :         }
    2637            0 :         else if (res < 0)
    2638              :         {
    2639            0 :                 actx_error(actx, "device prompt failed");
    2640            0 :                 return false;
    2641              :         }
    2642              : 
    2643            0 :         return true;
    2644            0 : }
    2645              : 
    2646              : /*
    2647              :  * Calls curl_global_init() in a thread-safe way.
    2648              :  *
    2649              :  * libcurl has stringent requirements for the thread context in which you call
    2650              :  * curl_global_init(), because it's going to try initializing a bunch of other
    2651              :  * libraries (OpenSSL, Winsock, etc). Recent versions of libcurl have improved
    2652              :  * the thread-safety situation, but there's a chicken-and-egg problem at
    2653              :  * runtime: you can't check the thread safety until you've initialized libcurl,
    2654              :  * which you can't do from within a thread unless you know it's thread-safe...
    2655              :  *
    2656              :  * Returns true if initialization was successful. Successful or not, this
    2657              :  * function will not try to reinitialize Curl on successive calls.
    2658              :  */
    2659              : static bool
    2660            0 : initialize_curl(PGconn *conn)
    2661              : {
    2662              :         /*
    2663              :          * Don't let the compiler play tricks with this variable. In the
    2664              :          * HAVE_THREADSAFE_CURL_GLOBAL_INIT case, we don't care if two threads
    2665              :          * enter simultaneously, but we do care if this gets set transiently to
    2666              :          * PG_BOOL_YES/NO in cases where that's not the final answer.
    2667              :          */
    2668              :         static volatile PGTernaryBool init_successful = PG_BOOL_UNKNOWN;
    2669              : #if HAVE_THREADSAFE_CURL_GLOBAL_INIT
    2670            0 :         curl_version_info_data *info;
    2671              : #endif
    2672              : 
    2673              : #if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
    2674              : 
    2675              :         /*
    2676              :          * Lock around the whole function. If a libpq client performs its own work
    2677              :          * with libcurl, it must either ensure that Curl is initialized safely
    2678              :          * before calling us (in which case our call will be a no-op), or else it
    2679              :          * must guard its own calls to curl_global_init() with a registered
    2680              :          * threadlock handler. See PQregisterThreadLock().
    2681              :          */
    2682              :         pglock_thread();
    2683              : #endif
    2684              : 
    2685              :         /*
    2686              :          * Skip initialization if we've already done it. (Curl tracks the number
    2687              :          * of calls; there's no point in incrementing the counter every time we
    2688              :          * connect.)
    2689              :          */
    2690            0 :         if (init_successful == PG_BOOL_YES)
    2691            0 :                 goto done;
    2692            0 :         else if (init_successful == PG_BOOL_NO)
    2693              :         {
    2694            0 :                 libpq_append_conn_error(conn,
    2695              :                                                                 "curl_global_init previously failed during OAuth setup");
    2696            0 :                 goto done;
    2697              :         }
    2698              : 
    2699              :         /*
    2700              :          * We know we've already initialized Winsock by this point (see
    2701              :          * pqMakeEmptyPGconn()), so we should be able to safely skip that bit. But
    2702              :          * we have to tell libcurl to initialize everything else, because other
    2703              :          * pieces of our client executable may already be using libcurl for their
    2704              :          * own purposes. If we initialize libcurl with only a subset of its
    2705              :          * features, we could break those other clients nondeterministically, and
    2706              :          * that would probably be a nightmare to debug.
    2707              :          *
    2708              :          * If some other part of the program has already called this, it's a
    2709              :          * no-op.
    2710              :          */
    2711            0 :         if (curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32) != CURLE_OK)
    2712              :         {
    2713            0 :                 libpq_append_conn_error(conn,
    2714              :                                                                 "curl_global_init failed during OAuth setup");
    2715            0 :                 init_successful = PG_BOOL_NO;
    2716            0 :                 goto done;
    2717              :         }
    2718              : 
    2719              : #if HAVE_THREADSAFE_CURL_GLOBAL_INIT
    2720              : 
    2721              :         /*
    2722              :          * If we determined at configure time that the Curl installation is
    2723              :          * thread-safe, our job here is much easier. We simply initialize above
    2724              :          * without any locking (concurrent or duplicated calls are fine in that
    2725              :          * situation), then double-check to make sure the runtime setting agrees,
    2726              :          * to try to catch silent downgrades.
    2727              :          */
    2728            0 :         info = curl_version_info(CURLVERSION_NOW);
    2729            0 :         if (!(info->features & CURL_VERSION_THREADSAFE))
    2730              :         {
    2731              :                 /*
    2732              :                  * In a downgrade situation, the damage is already done. Curl global
    2733              :                  * state may be corrupted. Be noisy.
    2734              :                  */
    2735            0 :                 libpq_append_conn_error(conn, "libcurl is no longer thread-safe\n"
    2736              :                                                                 "\tCurl initialization was reported thread-safe when libpq\n"
    2737              :                                                                 "\twas compiled, but the currently installed version of\n"
    2738              :                                                                 "\tlibcurl reports that it is not. Recompile libpq against\n"
    2739              :                                                                 "\tthe installed version of libcurl.");
    2740            0 :                 init_successful = PG_BOOL_NO;
    2741            0 :                 goto done;
    2742              :         }
    2743              : #endif
    2744              : 
    2745            0 :         init_successful = PG_BOOL_YES;
    2746              : 
    2747              : done:
    2748              : #if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
    2749              :         pgunlock_thread();
    2750              : #endif
    2751            0 :         return (init_successful == PG_BOOL_YES);
    2752            0 : }
    2753              : 
    2754              : /*
    2755              :  * The core nonblocking libcurl implementation. This will be called several
    2756              :  * times to pump the async engine.
    2757              :  *
    2758              :  * The architecture is based on PQconnectPoll(). The first half drives the
    2759              :  * connection state forward as necessary, returning if we're not ready to
    2760              :  * proceed to the next step yet. The second half performs the actual transition
    2761              :  * between states.
    2762              :  *
    2763              :  * You can trace the overall OAuth flow through the second half. It's linear
    2764              :  * until we get to the end, where we flip back and forth between
    2765              :  * OAUTH_STEP_TOKEN_REQUEST and OAUTH_STEP_WAIT_INTERVAL to regularly ping the
    2766              :  * provider.
    2767              :  */
    2768              : static PostgresPollingStatusType
    2769            0 : pg_fe_run_oauth_flow_impl(PGconn *conn)
    2770              : {
    2771            0 :         fe_oauth_state *state = conn_sasl_state(conn);
    2772            0 :         struct async_ctx *actx;
    2773            0 :         char       *oauth_token = NULL;
    2774            0 :         PQExpBuffer errbuf;
    2775              : 
    2776            0 :         if (!initialize_curl(conn))
    2777            0 :                 return PGRES_POLLING_FAILED;
    2778              : 
    2779            0 :         if (!state->async_ctx)
    2780              :         {
    2781              :                 /*
    2782              :                  * Create our asynchronous state, and hook it into the upper-level
    2783              :                  * OAuth state immediately, so any failures below won't leak the
    2784              :                  * context allocation.
    2785              :                  */
    2786            0 :                 actx = calloc(1, sizeof(*actx));
    2787            0 :                 if (!actx)
    2788              :                 {
    2789            0 :                         libpq_append_conn_error(conn, "out of memory");
    2790            0 :                         return PGRES_POLLING_FAILED;
    2791              :                 }
    2792              : 
    2793            0 :                 actx->mux = PGINVALID_SOCKET;
    2794            0 :                 actx->timerfd = -1;
    2795              : 
    2796              :                 /* Should we enable unsafe features? */
    2797            0 :                 actx->debugging = oauth_unsafe_debugging_enabled();
    2798              : 
    2799            0 :                 state->async_ctx = actx;
    2800              : 
    2801            0 :                 initPQExpBuffer(&actx->work_data);
    2802            0 :                 initPQExpBuffer(&actx->errbuf);
    2803              : 
    2804            0 :                 if (!setup_multiplexer(actx))
    2805            0 :                         goto error_return;
    2806              : 
    2807            0 :                 if (!setup_curl_handles(actx))
    2808            0 :                         goto error_return;
    2809            0 :         }
    2810              : 
    2811            0 :         actx = state->async_ctx;
    2812              : 
    2813            0 :         do
    2814              :         {
    2815              :                 /* By default, the multiplexer is the altsock. Reassign as desired. */
    2816            0 :                 set_conn_altsock(conn, actx->mux);
    2817              : 
    2818            0 :                 switch (actx->step)
    2819              :                 {
    2820              :                         case OAUTH_STEP_INIT:
    2821              :                                 break;
    2822              : 
    2823              :                         case OAUTH_STEP_DISCOVERY:
    2824              :                         case OAUTH_STEP_DEVICE_AUTHORIZATION:
    2825              :                         case OAUTH_STEP_TOKEN_REQUEST:
    2826              :                                 {
    2827            0 :                                         PostgresPollingStatusType status;
    2828              : 
    2829              :                                         /*
    2830              :                                          * Clear any expired timeout before calling back into
    2831              :                                          * Curl. Curl is not guaranteed to do this for us, because
    2832              :                                          * its API expects us to use single-shot (i.e.
    2833              :                                          * edge-triggered) timeouts, and ours are level-triggered
    2834              :                                          * via the mux.
    2835              :                                          *
    2836              :                                          * This can't be combined with the comb_multiplexer() call
    2837              :                                          * below: we might accidentally clear a short timeout that
    2838              :                                          * was both set and expired during the call to
    2839              :                                          * drive_request().
    2840              :                                          */
    2841            0 :                                         if (!drain_timer_events(actx, NULL))
    2842            0 :                                                 goto error_return;
    2843              : 
    2844              :                                         /* Move the request forward. */
    2845            0 :                                         status = drive_request(actx);
    2846              : 
    2847            0 :                                         if (status == PGRES_POLLING_FAILED)
    2848            0 :                                                 goto error_return;
    2849            0 :                                         else if (status == PGRES_POLLING_OK)
    2850            0 :                                                 break;  /* done! */
    2851              : 
    2852              :                                         /*
    2853              :                                          * This request is still running.
    2854              :                                          *
    2855              :                                          * Make sure that stale events don't cause us to come back
    2856              :                                          * early. (Currently, this can occur only with kqueue.) If
    2857              :                                          * this is forgotten, the multiplexer can get stuck in a
    2858              :                                          * signaled state and we'll burn CPU cycles pointlessly.
    2859              :                                          */
    2860            0 :                                         if (!comb_multiplexer(actx))
    2861            0 :                                                 goto error_return;
    2862              : 
    2863            0 :                                         return status;
    2864            0 :                                 }
    2865              : 
    2866              :                         case OAUTH_STEP_WAIT_INTERVAL:
    2867              :                                 {
    2868            0 :                                         bool            expired;
    2869              : 
    2870              :                                         /*
    2871              :                                          * The client application is supposed to wait until our
    2872              :                                          * timer expires before calling PQconnectPoll() again, but
    2873              :                                          * that might not happen. To avoid sending a token request
    2874              :                                          * early, check the timer before continuing.
    2875              :                                          */
    2876            0 :                                         if (!drain_timer_events(actx, &expired))
    2877            0 :                                                 goto error_return;
    2878              : 
    2879            0 :                                         if (!expired)
    2880              :                                         {
    2881            0 :                                                 set_conn_altsock(conn, actx->timerfd);
    2882            0 :                                                 return PGRES_POLLING_READING;
    2883              :                                         }
    2884              : 
    2885            0 :                                         break;
    2886            0 :                                 }
    2887              :                 }
    2888              : 
    2889              :                 /*
    2890              :                  * Each case here must ensure that actx->running is set while we're
    2891              :                  * waiting on some asynchronous work. Most cases rely on
    2892              :                  * start_request() to do that for them.
    2893              :                  */
    2894            0 :                 switch (actx->step)
    2895              :                 {
    2896              :                         case OAUTH_STEP_INIT:
    2897            0 :                                 actx->errctx = libpq_gettext("failed to fetch OpenID discovery document");
    2898            0 :                                 if (!start_discovery(actx, conn_oauth_discovery_uri(conn)))
    2899            0 :                                         goto error_return;
    2900              : 
    2901            0 :                                 actx->step = OAUTH_STEP_DISCOVERY;
    2902            0 :                                 break;
    2903              : 
    2904              :                         case OAUTH_STEP_DISCOVERY:
    2905            0 :                                 if (!finish_discovery(actx))
    2906            0 :                                         goto error_return;
    2907              : 
    2908            0 :                                 if (!check_issuer(actx, conn))
    2909            0 :                                         goto error_return;
    2910              : 
    2911            0 :                                 actx->errctx = libpq_gettext("cannot run OAuth device authorization");
    2912            0 :                                 if (!check_for_device_flow(actx))
    2913            0 :                                         goto error_return;
    2914              : 
    2915            0 :                                 actx->errctx = libpq_gettext("failed to obtain device authorization");
    2916            0 :                                 if (!start_device_authz(actx, conn))
    2917            0 :                                         goto error_return;
    2918              : 
    2919            0 :                                 actx->step = OAUTH_STEP_DEVICE_AUTHORIZATION;
    2920            0 :                                 break;
    2921              : 
    2922              :                         case OAUTH_STEP_DEVICE_AUTHORIZATION:
    2923            0 :                                 if (!finish_device_authz(actx))
    2924            0 :                                         goto error_return;
    2925              : 
    2926            0 :                                 actx->errctx = libpq_gettext("failed to obtain access token");
    2927            0 :                                 if (!start_token_request(actx, conn))
    2928            0 :                                         goto error_return;
    2929              : 
    2930            0 :                                 actx->step = OAUTH_STEP_TOKEN_REQUEST;
    2931            0 :                                 break;
    2932              : 
    2933              :                         case OAUTH_STEP_TOKEN_REQUEST:
    2934            0 :                                 if (!handle_token_response(actx, &oauth_token))
    2935            0 :                                         goto error_return;
    2936              : 
    2937              :                                 /*
    2938              :                                  * Hook any oauth_token into the PGconn immediately so that
    2939              :                                  * the allocation isn't lost in case of an error.
    2940              :                                  */
    2941            0 :                                 set_conn_oauth_token(conn, oauth_token);
    2942              : 
    2943            0 :                                 if (!actx->user_prompted)
    2944              :                                 {
    2945              :                                         /*
    2946              :                                          * Now that we know the token endpoint isn't broken, give
    2947              :                                          * the user the login instructions.
    2948              :                                          */
    2949            0 :                                         if (!prompt_user(actx, conn))
    2950            0 :                                                 goto error_return;
    2951              : 
    2952            0 :                                         actx->user_prompted = true;
    2953            0 :                                 }
    2954              : 
    2955            0 :                                 if (oauth_token)
    2956            0 :                                         break;          /* done! */
    2957              : 
    2958              :                                 /*
    2959              :                                  * Wait for the required interval before issuing the next
    2960              :                                  * request.
    2961              :                                  */
    2962            0 :                                 if (!set_timer(actx, actx->authz.interval * 1000))
    2963            0 :                                         goto error_return;
    2964              : 
    2965              :                                 /*
    2966              :                                  * No Curl requests are running, so we can simplify by having
    2967              :                                  * the client wait directly on the timerfd rather than the
    2968              :                                  * multiplexer.
    2969              :                                  */
    2970            0 :                                 set_conn_altsock(conn, actx->timerfd);
    2971              : 
    2972            0 :                                 actx->step = OAUTH_STEP_WAIT_INTERVAL;
    2973            0 :                                 actx->running = 1;
    2974            0 :                                 break;
    2975              : 
    2976              :                         case OAUTH_STEP_WAIT_INTERVAL:
    2977            0 :                                 actx->errctx = libpq_gettext("failed to obtain access token");
    2978            0 :                                 if (!start_token_request(actx, conn))
    2979            0 :                                         goto error_return;
    2980              : 
    2981            0 :                                 actx->step = OAUTH_STEP_TOKEN_REQUEST;
    2982            0 :                                 break;
    2983              :                 }
    2984              : 
    2985              :                 /*
    2986              :                  * The vast majority of the time, if we don't have a token at this
    2987              :                  * point, actx->running will be set. But there are some corner cases
    2988              :                  * where we can immediately loop back around; see start_request().
    2989              :                  */
    2990            0 :         } while (!oauth_token && !actx->running);
    2991              : 
    2992              :         /* If we've stored a token, we're done. Otherwise come back later. */
    2993            0 :         return oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
    2994              : 
    2995              : error_return:
    2996            0 :         errbuf = conn_errorMessage(conn);
    2997              : 
    2998              :         /*
    2999              :          * Assemble the three parts of our error: context, body, and detail. See
    3000              :          * also the documentation for struct async_ctx.
    3001              :          */
    3002            0 :         if (actx->errctx)
    3003            0 :                 appendPQExpBuffer(errbuf, "%s: ", actx->errctx);
    3004              : 
    3005            0 :         if (PQExpBufferDataBroken(actx->errbuf))
    3006            0 :                 appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
    3007              :         else
    3008            0 :                 appendPQExpBufferStr(errbuf, actx->errbuf.data);
    3009              : 
    3010            0 :         if (actx->curl_err[0])
    3011              :         {
    3012            0 :                 appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
    3013              : 
    3014              :                 /* Sometimes libcurl adds a newline to the error buffer. :( */
    3015            0 :                 if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
    3016              :                 {
    3017            0 :                         errbuf->data[errbuf->len - 2] = ')';
    3018            0 :                         errbuf->data[errbuf->len - 1] = '\0';
    3019            0 :                         errbuf->len--;
    3020            0 :                 }
    3021            0 :         }
    3022              : 
    3023            0 :         appendPQExpBufferChar(errbuf, '\n');
    3024              : 
    3025            0 :         return PGRES_POLLING_FAILED;
    3026            0 : }
    3027              : 
    3028              : /*
    3029              :  * The top-level entry point. This is a convenient place to put necessary
    3030              :  * wrapper logic before handing off to the true implementation, above.
    3031              :  */
    3032              : PostgresPollingStatusType
    3033            0 : pg_fe_run_oauth_flow(PGconn *conn)
    3034              : {
    3035            0 :         PostgresPollingStatusType result;
    3036            0 :         fe_oauth_state *state = conn_sasl_state(conn);
    3037            0 :         struct async_ctx *actx;
    3038              : #ifndef WIN32
    3039            0 :         sigset_t        osigset;
    3040            0 :         bool            sigpipe_pending;
    3041            0 :         bool            masked;
    3042              : 
    3043              :         /*---
    3044              :          * Ignore SIGPIPE on this thread during all Curl processing.
    3045              :          *
    3046              :          * Because we support multiple threads, we have to set up libcurl with
    3047              :          * CURLOPT_NOSIGNAL, which disables its default global handling of
    3048              :          * SIGPIPE. From the Curl docs:
    3049              :          *
    3050              :          *     libcurl makes an effort to never cause such SIGPIPE signals to
    3051              :          *     trigger, but some operating systems have no way to avoid them and
    3052              :          *     even on those that have there are some corner cases when they may
    3053              :          *     still happen, contrary to our desire.
    3054              :          *
    3055              :          * Note that libcurl is also at the mercy of its DNS resolution and SSL
    3056              :          * libraries; if any of them forget a MSG_NOSIGNAL then we're in trouble.
    3057              :          * Modern platforms and libraries seem to get it right, so this is a
    3058              :          * difficult corner case to exercise in practice, and unfortunately it's
    3059              :          * not really clear whether it's necessary in all cases.
    3060              :          */
    3061            0 :         masked = (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0);
    3062              : #endif
    3063              : 
    3064            0 :         result = pg_fe_run_oauth_flow_impl(conn);
    3065              : 
    3066              :         /*
    3067              :          * To assist with finding bugs in comb_multiplexer() and
    3068              :          * drain_timer_events(), when we're in debug mode, track the total number
    3069              :          * of calls to this function and print that at the end of the flow.
    3070              :          *
    3071              :          * Be careful that state->async_ctx could be NULL if early initialization
    3072              :          * fails during the first call.
    3073              :          */
    3074            0 :         actx = state->async_ctx;
    3075            0 :         Assert(actx || result == PGRES_POLLING_FAILED);
    3076              : 
    3077            0 :         if (actx && actx->debugging)
    3078              :         {
    3079            0 :                 actx->dbg_num_calls++;
    3080            0 :                 if (result == PGRES_POLLING_OK || result == PGRES_POLLING_FAILED)
    3081            0 :                         fprintf(stderr, "[libpq] total number of polls: %d\n",
    3082            0 :                                         actx->dbg_num_calls);
    3083            0 :         }
    3084              : 
    3085              : #ifndef WIN32
    3086            0 :         if (masked)
    3087              :         {
    3088              :                 /*
    3089              :                  * Undo the SIGPIPE mask. Assume we may have gotten EPIPE (we have no
    3090              :                  * way of knowing at this level).
    3091              :                  */
    3092            0 :                 pq_reset_sigpipe(&osigset, sigpipe_pending, true /* EPIPE, maybe */ );
    3093            0 :         }
    3094              : #endif
    3095              : 
    3096            0 :         return result;
    3097            0 : }
        

Generated by: LCOV version 2.3.2-1