PostgreSQL Source Code git master
Loading...
Searching...
No Matches
oauth-curl.c File Reference
#include "postgres_fe.h"
#include <curl/curl.h>
#include <math.h>
#include <unistd.h>
#include "common/jsonapi.h"
#include "mb/pg_wchar.h"
#include "oauth-curl.h"
#include "fe-auth-oauth.h"
#include "libpq-int.h"
#include "oauth-debug.h"
Include dependency graph for oauth-curl.c:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  provider
 
struct  device_authz
 
struct  token_error
 
struct  token
 
struct  async_ctx
 
struct  json_field
 
struct  oauth_parse
 

Macros

#define MAX_OAUTH_RESPONSE_SIZE   (256 * 1024)
 
#define MAX_OAUTH_NESTING_LEVEL   16
 
#define actx_error(ACTX, FMT, ...)    appendPQExpBuffer(&(ACTX)->errbuf, libpq_gettext(FMT), ##__VA_ARGS__)
 
#define actx_error_internal(ACTX, FMT, ...)    appendPQExpBuffer(&(ACTX)->errbuf, FMT, ##__VA_ARGS__)
 
#define actx_error_str(ACTX, S)    appendPQExpBufferStr(&(ACTX)->errbuf, S)
 
#define CHECK_MSETOPT(ACTX, OPT, VAL, FAILACTION)
 
#define CHECK_SETOPT(ACTX, OPT, VAL, FAILACTION)
 
#define CHECK_GETINFO(ACTX, INFO, OUT, FAILACTION)
 
#define PG_OAUTH_REQUIRED   true
 
#define PG_OAUTH_OPTIONAL   false
 
#define oauth_parse_set_error(ctx, fmt, ...)    appendPQExpBuffer((ctx)->errbuf, libpq_gettext(fmt), ##__VA_ARGS__)
 
#define oauth_parse_set_error_internal(ctx, fmt, ...)    appendPQExpBuffer((ctx)->errbuf, fmt, ##__VA_ARGS__)
 
#define CURL_IGNORE_DEPRECATION(x)   x
 
#define PG_CURL_IGNORE_DEPRECATION(x)   CURL_IGNORE_DEPRECATION(x;)
 
#define HTTPS_SCHEME   "https://"
 
#define OAUTH_GRANT_TYPE_DEVICE_CODE   "urn:ietf:params:oauth:grant-type:device_code"
 

Enumerations

enum  OAuthStep {
  OAUTH_STEP_INIT = 0 , OAUTH_STEP_DISCOVERY , OAUTH_STEP_DEVICE_AUTHORIZATION , OAUTH_STEP_TOKEN_REQUEST ,
  OAUTH_STEP_WAIT_INTERVAL
}
 

Functions

static void free_provider (struct provider *provider)
 
static void free_device_authz (struct device_authz *authz)
 
static void free_token_error (struct token_error *err)
 
static void free_token (struct token *tok)
 
static void free_async_ctx (struct async_ctx *actx)
 
static void pg_fe_cleanup_oauth_flow (PGconn *conn, PGoauthBearerRequest *request)
 
static void append_actx_error (PGoauthBearerRequestV2 *req, struct async_ctx *actx)
 
static void report_type_mismatch (struct oauth_parse *ctx)
 
static JsonParseErrorType oauth_json_object_start (void *state)
 
static JsonParseErrorType oauth_json_object_field_start (void *state, char *name, bool isnull)
 
static JsonParseErrorType oauth_json_object_end (void *state)
 
static JsonParseErrorType oauth_json_array_start (void *state)
 
static JsonParseErrorType oauth_json_array_end (void *state)
 
static JsonParseErrorType oauth_json_scalar (void *state, char *token, JsonTokenType type)
 
static bool check_content_type (struct async_ctx *actx, const char *type)
 
static bool parse_oauth_json (struct async_ctx *actx, const struct json_field *fields)
 
static bool parse_provider (struct async_ctx *actx, struct provider *provider)
 
static double parse_json_number (const char *s)
 
static int parse_interval (struct async_ctx *actx, const char *interval_str)
 
static int parse_expires_in (struct async_ctx *actx, const char *expires_in_str)
 
static bool parse_device_authz (struct async_ctx *actx, struct device_authz *authz)
 
static bool parse_token_error (struct async_ctx *actx, struct token_error *err)
 
static void record_token_error (struct async_ctx *actx, const struct token_error *err)
 
static bool parse_access_token (struct async_ctx *actx, struct token *tok)
 
static bool setup_multiplexer (struct async_ctx *actx)
 
static int register_socket (CURL *curl, curl_socket_t socket, int what, void *ctx, void *socketp)
 
static bool comb_multiplexer (struct async_ctx *actx)
 
static bool set_timer (struct async_ctx *actx, long timeout)
 
static int timer_expired (struct async_ctx *actx)
 
static int register_timer (CURLM *curlm, long timeout, void *ctx)
 
static bool drain_timer_events (struct async_ctx *actx, bool *was_expired)
 
static int debug_callback (CURL *handle, curl_infotype type, char *data, size_t size, void *clientp)
 
static bool setup_curl_handles (struct async_ctx *actx)
 
static size_t append_data (char *buf, size_t size, size_t nmemb, void *userdata)
 
static bool start_request (struct async_ctx *actx)
 
static PostgresPollingStatusType drive_request (struct async_ctx *actx)
 
static void append_urlencoded (PQExpBuffer buf, const char *s)
 
static charurlencode (const char *s)
 
static void build_urlencoded (PQExpBuffer buf, const char *key, const char *value)
 
static bool start_discovery (struct async_ctx *actx, const char *discovery_uri)
 
static bool finish_discovery (struct async_ctx *actx)
 
static bool check_issuer (struct async_ctx *actx, PGconn *conn)
 
static bool check_for_device_flow (struct async_ctx *actx)
 
static bool add_client_identification (struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
 
static bool start_device_authz (struct async_ctx *actx, PGconn *conn)
 
static bool finish_device_authz (struct async_ctx *actx)
 
static bool start_token_request (struct async_ctx *actx, PGconn *conn)
 
static bool finish_token_request (struct async_ctx *actx, struct token *tok)
 
static bool handle_token_response (struct async_ctx *actx, char **token)
 
static bool prompt_user (struct async_ctx *actx, PGconn *conn)
 
static bool initialize_curl (PGoauthBearerRequestV2 *req)
 
static PostgresPollingStatusType pg_fe_run_oauth_flow_impl (PGconn *conn, PGoauthBearerRequestV2 *request, int *altsock)
 
static PostgresPollingStatusType pg_fe_run_oauth_flow (PGconn *conn, struct PGoauthBearerRequest *request, int *altsock)
 
int pg_start_oauthbearer (PGconn *conn, PGoauthBearerRequestV2 *request)
 

Macro Definition Documentation

◆ actx_error

#define actx_error (   ACTX,
  FMT,
  ... 
)     appendPQExpBuffer(&(ACTX)->errbuf, libpq_gettext(FMT), ##__VA_ARGS__)

Definition at line 419 of file oauth-curl.c.

◆ actx_error_internal

#define actx_error_internal (   ACTX,
  FMT,
  ... 
)     appendPQExpBuffer(&(ACTX)->errbuf, FMT, ##__VA_ARGS__)

Definition at line 422 of file oauth-curl.c.

◆ actx_error_str

#define actx_error_str (   ACTX,
  S 
)     appendPQExpBufferStr(&(ACTX)->errbuf, S)

Definition at line 425 of file oauth-curl.c.

◆ CHECK_GETINFO

#define CHECK_GETINFO (   ACTX,
  INFO,
  OUT,
  FAILACTION 
)
Value:
do { \
struct async_ctx *_actx = (ACTX); \
actx_error(_actx, "failed to get %s from OAuth response: %s",\
} \
} while (0)
#define INFO
Definition elog.h:35
static int fb(int x)

Definition at line 455 of file oauth-curl.c.

456 { \
457 struct async_ctx *_actx = (ACTX); \
459 if (_getinfoerr) { \
460 actx_error(_actx, "failed to get %s from OAuth response: %s",\
462 FAILACTION; \
463 } \
464 } while (0)

◆ CHECK_MSETOPT

#define CHECK_MSETOPT (   ACTX,
  OPT,
  VAL,
  FAILACTION 
)
Value:
do { \
struct async_ctx *_actx = (ACTX); \
actx_error(_actx, "failed to set %s on OAuth connection: %s",\
} \
} while (0)
#define VAL
Definition _int.h:162

Definition at line 433 of file oauth-curl.c.

434 { \
435 struct async_ctx *_actx = (ACTX); \
437 if (_setopterr) { \
438 actx_error(_actx, "failed to set %s on OAuth connection: %s",\
440 FAILACTION; \
441 } \
442 } while (0)

◆ CHECK_SETOPT

#define CHECK_SETOPT (   ACTX,
  OPT,
  VAL,
  FAILACTION 
)
Value:
do { \
struct async_ctx *_actx = (ACTX); \
actx_error(_actx, "failed to set %s on OAuth connection: %s",\
} \
} while (0)

Definition at line 444 of file oauth-curl.c.

445 { \
446 struct async_ctx *_actx = (ACTX); \
448 if (_setopterr) { \
449 actx_error(_actx, "failed to set %s on OAuth connection: %s",\
451 FAILACTION; \
452 } \
453 } while (0)

◆ CURL_IGNORE_DEPRECATION

#define CURL_IGNORE_DEPRECATION (   x)    x

Definition at line 1957 of file oauth-curl.c.

◆ HTTPS_SCHEME

#define HTTPS_SCHEME   "https://"

Definition at line 2267 of file oauth-curl.c.

◆ MAX_OAUTH_NESTING_LEVEL

#define MAX_OAUTH_NESTING_LEVEL   16

Definition at line 89 of file oauth-curl.c.

◆ MAX_OAUTH_RESPONSE_SIZE

#define MAX_OAUTH_RESPONSE_SIZE   (256 * 1024)

Definition at line 75 of file oauth-curl.c.

◆ OAUTH_GRANT_TYPE_DEVICE_CODE

#define OAUTH_GRANT_TYPE_DEVICE_CODE   "urn:ietf:params:oauth:grant-type:device_code"

Definition at line 2268 of file oauth-curl.c.

◆ oauth_parse_set_error

#define oauth_parse_set_error (   ctx,
  fmt,
  ... 
)     appendPQExpBuffer((ctx)->errbuf, libpq_gettext(fmt), ##__VA_ARGS__)

Definition at line 509 of file oauth-curl.c.

◆ oauth_parse_set_error_internal

#define oauth_parse_set_error_internal (   ctx,
  fmt,
  ... 
)     appendPQExpBuffer((ctx)->errbuf, fmt, ##__VA_ARGS__)

Definition at line 512 of file oauth-curl.c.

◆ PG_CURL_IGNORE_DEPRECATION

#define PG_CURL_IGNORE_DEPRECATION (   x)    CURL_IGNORE_DEPRECATION(x;)

Definition at line 1964 of file oauth-curl.c.

◆ PG_OAUTH_OPTIONAL

#define PG_OAUTH_OPTIONAL   false

Definition at line 497 of file oauth-curl.c.

◆ PG_OAUTH_REQUIRED

#define PG_OAUTH_REQUIRED   true

Definition at line 496 of file oauth-curl.c.

Enumeration Type Documentation

◆ OAuthStep

Enumerator
OAUTH_STEP_INIT 
OAUTH_STEP_DISCOVERY 
OAUTH_STEP_DEVICE_AUTHORIZATION 
OAUTH_STEP_TOKEN_REQUEST 
OAUTH_STEP_WAIT_INTERVAL 

Definition at line 206 of file oauth-curl.c.

207{
208 OAUTH_STEP_INIT = 0,
213};
@ OAUTH_STEP_DEVICE_AUTHORIZATION
Definition oauth-curl.c:210
@ OAUTH_STEP_WAIT_INTERVAL
Definition oauth-curl.c:212
@ OAUTH_STEP_INIT
Definition oauth-curl.c:208
@ OAUTH_STEP_DISCOVERY
Definition oauth-curl.c:209
@ OAUTH_STEP_TOKEN_REQUEST
Definition oauth-curl.c:211

Function Documentation

◆ add_client_identification()

static bool add_client_identification ( struct async_ctx actx,
PQExpBuffer  reqbody,
PGconn conn 
)
static

Definition at line 2335 of file oauth-curl.c.

2336{
2337 const char *oauth_client_id = actx->client_id;
2338 const char *oauth_client_secret = actx->client_secret;
2339
2340 bool success = false;
2341 char *username = NULL;
2342 char *password = NULL;
2343
2344 if (oauth_client_secret) /* Zero-length secrets are permitted! */
2345 {
2346 /*----
2347 * Use HTTP Basic auth to send the client_id and secret. Per RFC 6749,
2348 * Sec. 2.3.1,
2349 *
2350 * Including the client credentials in the request-body using the
2351 * two parameters is NOT RECOMMENDED and SHOULD be limited to
2352 * clients unable to directly utilize the HTTP Basic authentication
2353 * scheme (or other password-based HTTP authentication schemes).
2354 *
2355 * Additionally:
2356 *
2357 * The client identifier is encoded using the
2358 * "application/x-www-form-urlencoded" encoding algorithm per Appendix
2359 * B, and the encoded value is used as the username; the client
2360 * password is encoded using the same algorithm and used as the
2361 * password.
2362 *
2363 * (Appendix B modifies application/x-www-form-urlencoded by requiring
2364 * an initial UTF-8 encoding step. Since the client ID and secret must
2365 * both be 7-bit ASCII -- RFC 6749 Appendix A -- we don't worry about
2366 * that in this function.)
2367 *
2368 * client_id is not added to the request body in this case. Not only
2369 * would it be redundant, but some providers in the wild (e.g. Okta)
2370 * refuse to accept it.
2371 */
2372 username = urlencode(oauth_client_id);
2373 password = urlencode(oauth_client_secret);
2374
2375 if (!username || !password)
2376 {
2377 actx_error(actx, "out of memory");
2378 goto cleanup;
2379 }
2380
2384
2385 actx->used_basic_auth = true;
2386 }
2387 else
2388 {
2389 /*
2390 * If we're not otherwise authenticating, client_id is REQUIRED in the
2391 * request body.
2392 */
2393 build_urlencoded(reqbody, "client_id", oauth_client_id);
2394
2396 actx->used_basic_auth = false;
2397 }
2398
2399 success = true;
2400
2401cleanup:
2402 free(username);
2403 free(password);
2404
2405 return success;
2406}
static void cleanup(void)
Definition bootstrap.c:886
static bool success
Definition initdb.c:188
static char * username
Definition initdb.c:153
static char * urlencode(const char *s)
static void build_urlencoded(PQExpBuffer buf, const char *key, const char *value)
#define CHECK_SETOPT(ACTX, OPT, VAL, FAILACTION)
Definition oauth-curl.c:444
#define actx_error(ACTX, FMT,...)
Definition oauth-curl.c:419
#define free(a)
static char * password
Definition streamutil.c:51

References actx_error, build_urlencoded(), CHECK_SETOPT, cleanup(), fb(), free, password, success, urlencode(), and username.

Referenced by start_device_authz(), and start_token_request().

◆ append_actx_error()

static void append_actx_error ( PGoauthBearerRequestV2 req,
struct async_ctx actx 
)
static

Definition at line 379 of file oauth-curl.c.

380{
381 PQExpBuffer errbuf = &actx->work_data;
382
383 resetPQExpBuffer(errbuf);
384
385 /*
386 * Assemble the three parts of our error: context, body, and detail. See
387 * also the documentation for struct async_ctx.
388 */
389 if (actx->errctx)
390 appendPQExpBuffer(errbuf, "%s: ", actx->errctx);
391
392 if (PQExpBufferDataBroken(actx->errbuf))
393 appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
394 else
395 appendPQExpBufferStr(errbuf, actx->errbuf.data);
396
397 if (actx->curl_err[0])
398 {
399 appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
400
401 /* Sometimes libcurl adds a newline to the error buffer. :( */
402 if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
403 {
404 errbuf->data[errbuf->len - 2] = ')';
405 errbuf->data[errbuf->len - 1] = '\0';
406 errbuf->len--;
407 }
408 }
409
410 req->error = errbuf->data;
411}
#define libpq_gettext(x)
Definition oauth-utils.h:44
void resetPQExpBuffer(PQExpBuffer str)
void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
void appendPQExpBufferStr(PQExpBuffer str, const char *data)
#define PQExpBufferDataBroken(buf)
Definition pqexpbuffer.h:67

References appendPQExpBuffer(), appendPQExpBufferStr(), PQExpBufferData::data, async_ctx::errbuf, fb(), PQExpBufferData::len, libpq_gettext, PQExpBufferDataBroken, and resetPQExpBuffer().

Referenced by pg_fe_run_oauth_flow_impl(), and pg_start_oauthbearer().

◆ append_data()

static size_t append_data ( char buf,
size_t  size,
size_t  nmemb,
void userdata 
)
static

Definition at line 1876 of file oauth-curl.c.

1877{
1878 struct async_ctx *actx = userdata;
1879 PQExpBuffer resp = &actx->work_data;
1880 size_t len = size * nmemb;
1881
1882 /* In case we receive data over the threshold, abort the transfer */
1883 if ((resp->len + len) > MAX_OAUTH_RESPONSE_SIZE)
1884 {
1885 actx_error(actx, "response is too large");
1886 return 0;
1887 }
1888
1889 /* The data passed from libcurl is not null-terminated */
1891
1892 /*
1893 * Signal an error in order to abort the transfer in case we ran out of
1894 * memory in accepting the data.
1895 */
1897 {
1898 actx_error(actx, "out of memory");
1899 return 0;
1900 }
1901
1902 return len;
1903}
#define MAX_OAUTH_RESPONSE_SIZE
Definition oauth-curl.c:75
const void size_t len
static char buf[DEFAULT_XLOG_SEG_SIZE]
void appendBinaryPQExpBuffer(PQExpBuffer str, const char *data, size_t datalen)
#define PQExpBufferBroken(str)
Definition pqexpbuffer.h:59

References actx_error, appendBinaryPQExpBuffer(), buf, fb(), len, MAX_OAUTH_RESPONSE_SIZE, and PQExpBufferBroken.

Referenced by restore_one_database(), RestoreArchive(), SetOutput(), and start_request().

◆ append_urlencoded()

static void append_urlencoded ( PQExpBuffer  buf,
const char s 
)
static

Definition at line 2070 of file oauth-curl.c.

2071{
2072 char *escaped;
2073 char *haystack;
2074 char *match;
2075
2076 /* The first parameter to curl_easy_escape is deprecated by Curl */
2077 escaped = curl_easy_escape(NULL, s, 0);
2078 if (!escaped)
2079 {
2080 termPQExpBuffer(buf); /* mark the buffer broken */
2081 return;
2082 }
2083
2084 /*
2085 * curl_easy_escape() almost does what we want, but we need the
2086 * query-specific flavor which uses '+' instead of '%20' for spaces. The
2087 * Curl command-line tool does this with a simple search-and-replace, so
2088 * follow its lead.
2089 */
2090 haystack = escaped;
2091
2092 while ((match = strstr(haystack, "%20")) != NULL)
2093 {
2094 /* Append the unmatched portion, followed by the plus sign. */
2097
2098 /* Keep searching after the match. */
2099 haystack = match + 3 /* strlen("%20") */ ;
2100 }
2101
2102 /* Push the remainder of the string onto the buffer. */
2104
2106}
void appendPQExpBufferChar(PQExpBuffer str, char ch)
void termPQExpBuffer(PQExpBuffer str)

References appendBinaryPQExpBuffer(), appendPQExpBufferChar(), appendPQExpBufferStr(), buf, fb(), and termPQExpBuffer().

Referenced by build_urlencoded(), and urlencode().

◆ build_urlencoded()

static void build_urlencoded ( PQExpBuffer  buf,
const char key,
const char value 
)
static

Definition at line 2128 of file oauth-curl.c.

2129{
2130 if (buf->len)
2132
2133 append_urlencoded(buf, key);
2136}
static struct @177 value
static void append_urlencoded(PQExpBuffer buf, const char *s)

References append_urlencoded(), appendPQExpBufferChar(), buf, and value.

Referenced by add_client_identification(), start_device_authz(), and start_token_request().

◆ check_content_type()

static bool check_content_type ( struct async_ctx actx,
const char type 
)
static

Definition at line 812 of file oauth-curl.c.

813{
814 const size_t type_len = strlen(type);
815 char *content_type;
816
817 CHECK_GETINFO(actx, CURLINFO_CONTENT_TYPE, &content_type, return false);
818
819 if (!content_type)
820 {
821 actx_error(actx, "no content type was provided");
822 return false;
823 }
824
825 /*
826 * We need to perform a length limited comparison and not compare the
827 * whole string.
828 */
829 if (pg_strncasecmp(content_type, type, type_len) != 0)
830 goto fail;
831
832 /* On an exact match, we're done. */
833 Assert(strlen(content_type) >= type_len);
834 if (content_type[type_len] == '\0')
835 return true;
836
837 /*
838 * Only a semicolon (optionally preceded by HTTP optional whitespace) is
839 * acceptable after the prefix we checked. This marks the start of media
840 * type parameters, which we currently have no use for.
841 */
842 for (size_t i = type_len; content_type[i]; ++i)
843 {
844 switch (content_type[i])
845 {
846 case ';':
847 return true; /* success! */
848
849 case ' ':
850 case '\t':
851 /* HTTP optional whitespace allows only spaces and htabs. */
852 break;
853
854 default:
855 goto fail;
856 }
857 }
858
859fail:
860 actx_error(actx, "unexpected content type: \"%s\"", content_type);
861 return false;
862}
#define Assert(condition)
Definition c.h:943
int i
Definition isn.c:77
#define CHECK_GETINFO(ACTX, INFO, OUT, FAILACTION)
Definition oauth-curl.c:455
int pg_strncasecmp(const char *s1, const char *s2, size_t n)
const char * type

References actx_error, Assert, CHECK_GETINFO, fb(), i, pg_strncasecmp(), and type.

Referenced by parse_oauth_json().

◆ check_for_device_flow()

static bool check_for_device_flow ( struct async_ctx actx)
static

Definition at line 2276 of file oauth-curl.c.

2277{
2278 const struct provider *provider = &actx->provider;
2279
2280 Assert(provider->issuer); /* ensured by parse_provider() */
2281 Assert(provider->token_endpoint); /* ensured by parse_provider() */
2282
2284 {
2286 "issuer \"%s\" does not provide a device authorization endpoint",
2287 provider->issuer);
2288 return false;
2289 }
2290
2291 /*
2292 * The original implementation checked that OAUTH_GRANT_TYPE_DEVICE_CODE
2293 * was present in the discovery document's grant_types_supported list. MS
2294 * Entra does not advertise this grant type, though, and since it doesn't
2295 * make sense to stand up a device_authorization_endpoint without also
2296 * accepting device codes at the token_endpoint, that's the only thing we
2297 * currently require.
2298 */
2299
2300 /*
2301 * Although libcurl will fail later if the URL contains an unsupported
2302 * scheme, that error message is going to be a bit opaque. This is a
2303 * decent time to bail out if we're not using HTTPS for the endpoints
2304 * we'll use for the flow.
2305 */
2306 if ((actx->debug_flags & OAUTHDEBUG_UNSAFE_HTTP) == 0)
2307 {
2310 {
2312 "device authorization endpoint \"%s\" must use HTTPS",
2314 return false;
2315 }
2316
2319 {
2321 "token endpoint \"%s\" must use HTTPS",
2323 return false;
2324 }
2325 }
2326
2327 return true;
2328}
#define HTTPS_SCHEME
#define OAUTHDEBUG_UNSAFE_HTTP
Definition oauth-debug.h:38
char * device_authorization_endpoint
Definition oauth-curl.c:110
char * issuer
Definition oauth-curl.c:108
char * token_endpoint
Definition oauth-curl.c:109

References actx_error, Assert, provider::device_authorization_endpoint, fb(), HTTPS_SCHEME, provider::issuer, OAUTHDEBUG_UNSAFE_HTTP, pg_strncasecmp(), and provider::token_endpoint.

Referenced by pg_fe_run_oauth_flow_impl().

◆ check_issuer()

static bool check_issuer ( struct async_ctx actx,
PGconn conn 
)
static

Definition at line 2231 of file oauth-curl.c.

2232{
2233 const struct provider *provider = &actx->provider;
2234 const char *oauth_issuer_id = actx->issuer_id;
2235
2236 Assert(oauth_issuer_id); /* ensured by setup_oauth_parameters() */
2237 Assert(provider->issuer); /* ensured by parse_provider() */
2238
2239 /*---
2240 * We require strict equality for issuer identifiers -- no path or case
2241 * normalization, no substitution of default ports and schemes, etc. This
2242 * is done to match the rules in OIDC Discovery Sec. 4.3 for config
2243 * validation:
2244 *
2245 * The issuer value returned MUST be identical to the Issuer URL that
2246 * was used as the prefix to /.well-known/openid-configuration to
2247 * retrieve the configuration information.
2248 *
2249 * as well as the rules set out in RFC 9207 for avoiding mix-up attacks:
2250 *
2251 * Clients MUST then [...] compare the result to the issuer identifier
2252 * of the authorization server where the authorization request was
2253 * sent to. This comparison MUST use simple string comparison as defined
2254 * in Section 6.2.1 of [RFC3986].
2255 */
2256 if (strcmp(oauth_issuer_id, provider->issuer) != 0)
2257 {
2259 "the issuer identifier (%s) does not match oauth_issuer (%s)",
2260 provider->issuer, oauth_issuer_id);
2261 return false;
2262 }
2263
2264 return true;
2265}

References actx_error, Assert, fb(), and provider::issuer.

Referenced by pg_fe_run_oauth_flow_impl().

◆ comb_multiplexer()

static bool comb_multiplexer ( struct async_ctx actx)
static

Definition at line 1449 of file oauth-curl.c.

1450{
1451#if defined(HAVE_SYS_EPOLL_H)
1452 /* The epoll implementation doesn't hold onto stale events. */
1453 return true;
1454#elif defined(HAVE_SYS_EVENT_H)
1455 struct timespec timeout = {0};
1456 struct kevent ev;
1457
1458 /*
1459 * Try to read a single pending event. We can actually ignore the result:
1460 * either we found an event to process, in which case the multiplexer is
1461 * correctly readable for that event at minimum, and it doesn't matter if
1462 * there are any stale events; or we didn't find any, in which case the
1463 * kernel will have discarded any stale events as it traveled to the end
1464 * of the queue.
1465 *
1466 * Note that this depends on our registrations being level-triggered --
1467 * even the timer, so we use a chained kqueue for that instead of an
1468 * EVFILT_TIMER on the top-level mux. If we used edge-triggered events,
1469 * this call would improperly discard them.
1470 */
1471 if (kevent(actx->mux, NULL, 0, &ev, 1, &timeout) < 0)
1472 {
1473 actx_error_internal(actx, "could not comb kqueue: %m");
1474 return false;
1475 }
1476
1477 return true;
1478#else
1479#error comb_multiplexer is not implemented on this platform
1480#endif
1481}
#define actx_error_internal(ACTX, FMT,...)
Definition oauth-curl.c:422

References actx_error_internal, and fb().

Referenced by pg_fe_run_oauth_flow_impl().

◆ debug_callback()

static int debug_callback ( CURL handle,
curl_infotype  type,
char data,
size_t  size,
void clientp 
)
static

Definition at line 1672 of file oauth-curl.c.

1674{
1675 const char *prefix;
1676 bool printed_prefix = false;
1678
1679 /* Prefixes are modeled off of the default libcurl debug output. */
1680 switch (type)
1681 {
1682 case CURLINFO_TEXT:
1683 prefix = "*";
1684 break;
1685
1686 case CURLINFO_HEADER_IN: /* fall through */
1687 case CURLINFO_DATA_IN:
1688 prefix = "<";
1689 break;
1690
1691 case CURLINFO_HEADER_OUT: /* fall through */
1692 case CURLINFO_DATA_OUT:
1693 prefix = ">";
1694 break;
1695
1696 default:
1697 return 0;
1698 }
1699
1701
1702 /*
1703 * Split the output into lines for readability; sometimes multiple headers
1704 * are included in a single call. We also don't allow unprintable ASCII
1705 * through without a basic <XX> escape.
1706 */
1707 for (int i = 0; i < size; i++)
1708 {
1709 char c = data[i];
1710
1711 if (!printed_prefix)
1712 {
1713 appendPQExpBuffer(&buf, "[libcurl] %s ", prefix);
1714 printed_prefix = true;
1715 }
1716
1717 if (c >= 0x20 && c <= 0x7E)
1719 else if ((type == CURLINFO_HEADER_IN
1721 || type == CURLINFO_TEXT)
1722 && (c == '\r' || c == '\n'))
1723 {
1724 /*
1725 * Don't bother emitting <0D><0A> for headers and text; it's not
1726 * helpful noise.
1727 */
1728 }
1729 else
1730 appendPQExpBuffer(&buf, "<%02X>", c);
1731
1732 if (c == '\n')
1733 {
1735 printed_prefix = false;
1736 }
1737 }
1738
1739 if (printed_prefix)
1740 appendPQExpBufferChar(&buf, '\n'); /* finish the line */
1741
1742 fprintf(stderr, "%s", buf.data);
1744 return 0;
1745}
#define fprintf(file, fmt, msg)
Definition cubescan.l:21
const void * data
void initPQExpBuffer(PQExpBuffer str)
Definition pqexpbuffer.c:90
char * c

References appendPQExpBuffer(), appendPQExpBufferChar(), buf, data, fb(), fprintf, i, initPQExpBuffer(), termPQExpBuffer(), and type.

Referenced by setup_curl_handles().

◆ drain_timer_events()

static bool drain_timer_events ( struct async_ctx actx,
bool was_expired 
)
static

Definition at line 1640 of file oauth-curl.c.

1641{
1642 int res;
1643
1644 res = timer_expired(actx);
1645 if (res < 0)
1646 return false;
1647
1648 if (res > 0)
1649 {
1650 /*
1651 * Timer is expired. We could drain the event manually from the
1652 * timerfd, but it's easier to simply disable it; that keeps the
1653 * platform-specific code in set_timer().
1654 */
1655 if (!set_timer(actx, -1))
1656 return false;
1657 }
1658
1659 if (was_expired)
1660 *was_expired = (res > 0);
1661
1662 return true;
1663}
static bool set_timer(struct async_ctx *actx, long timeout)
static int timer_expired(struct async_ctx *actx)

References fb(), set_timer(), and timer_expired().

Referenced by pg_fe_run_oauth_flow_impl().

◆ drive_request()

static PostgresPollingStatusType drive_request ( struct async_ctx actx)
static

Definition at line 1971 of file oauth-curl.c.

1972{
1973 CURLMcode err;
1974 CURLMsg *msg;
1975 int msgs_left;
1976 bool done;
1977
1978 if (actx->running)
1979 {
1980 /*---
1981 * There's an async request in progress. Pump the multi handle.
1982 *
1983 * curl_multi_socket_all() is officially deprecated, because it's
1984 * inefficient and pointless if your event loop has already handed you
1985 * the exact sockets that are ready. But that's not our use case --
1986 * our client has no way to tell us which sockets are ready. (They
1987 * don't even know there are sockets to begin with.)
1988 *
1989 * We can grab the list of triggered events from the multiplexer
1990 * ourselves, but that's effectively what curl_multi_socket_all() is
1991 * going to do. And there are currently no plans for the Curl project
1992 * to remove or break this API, so ignore the deprecation. See
1993 *
1994 * https://curl.se/mail/lib-2024-11/0028.html
1995 */
1998 &actx->running));
1999
2000 if (err)
2001 {
2002 actx_error(actx, "asynchronous HTTP request failed: %s",
2004 return PGRES_POLLING_FAILED;
2005 }
2006
2007 if (actx->running)
2008 {
2009 /* We'll come back again. */
2010 return PGRES_POLLING_READING;
2011 }
2012 }
2013
2014 done = false;
2015 while ((msg = curl_multi_info_read(actx->curlm, &msgs_left)) != NULL)
2016 {
2017 if (msg->msg != CURLMSG_DONE)
2018 {
2019 /*
2020 * Future libcurl versions may define new message types; we don't
2021 * know how to handle them, so we'll ignore them.
2022 */
2023 continue;
2024 }
2025
2026 /* First check the status of the request itself. */
2027 if (msg->data.result != CURLE_OK)
2028 {
2029 /*
2030 * If a more specific error hasn't already been reported, use
2031 * libcurl's description.
2032 */
2033 if (actx->errbuf.len == 0)
2034 actx_error_str(actx, curl_easy_strerror(msg->data.result));
2035
2036 return PGRES_POLLING_FAILED;
2037 }
2038
2039 /* Now remove the finished handle; we'll add it back later if needed. */
2040 err = curl_multi_remove_handle(actx->curlm, msg->easy_handle);
2041 if (err)
2042 {
2043 actx_error(actx, "libcurl easy handle removal failed: %s",
2045 return PGRES_POLLING_FAILED;
2046 }
2047
2048 done = true;
2049 }
2050
2051 /* Sanity check. */
2052 if (!done)
2053 {
2054 actx_error(actx, "no result was retrieved for the finished handle");
2055 return PGRES_POLLING_FAILED;
2056 }
2057
2058 return PGRES_POLLING_OK;
2059}
void err(int eval, const char *fmt,...)
Definition err.c:43
@ PGRES_POLLING_OK
Definition libpq-fe.h:124
@ PGRES_POLLING_READING
Definition libpq-fe.h:122
@ PGRES_POLLING_FAILED
Definition libpq-fe.h:121
#define PG_CURL_IGNORE_DEPRECATION(x)
#define actx_error_str(ACTX, S)
Definition oauth-curl.c:425

References actx_error, actx_error_str, err(), fb(), PG_CURL_IGNORE_DEPRECATION, PGRES_POLLING_FAILED, PGRES_POLLING_OK, and PGRES_POLLING_READING.

Referenced by pg_fe_run_oauth_flow_impl().

◆ finish_device_authz()

static bool finish_device_authz ( struct async_ctx actx)
static

Definition at line 2449 of file oauth-curl.c.

2450{
2451 long response_code;
2452
2454
2455 /*
2456 * Per RFC 8628, Section 3, a successful device authorization response
2457 * uses 200 OK.
2458 */
2459 if (response_code == 200)
2460 {
2461 actx->errctx = libpq_gettext("failed to parse device authorization");
2462 if (!parse_device_authz(actx, &actx->authz))
2463 return false; /* error message already set */
2464
2465 return true;
2466 }
2467
2468 /*
2469 * The device authorization endpoint uses the same error response as the
2470 * token endpoint, so the error handling roughly follows
2471 * finish_token_request(). The key difference is that an error here is
2472 * immediately fatal.
2473 */
2474 if (response_code == 400 || response_code == 401)
2475 {
2476 struct token_error err = {0};
2477
2478 if (!parse_token_error(actx, &err))
2479 {
2481 return false;
2482 }
2483
2484 /* Copy the token error into the context error buffer */
2486
2488 return false;
2489 }
2490
2491 /* Any other response codes are considered invalid */
2492 actx_error(actx, "unexpected response code %ld", response_code);
2493 return false;
2494}
static bool parse_token_error(struct async_ctx *actx, struct token_error *err)
static void record_token_error(struct async_ctx *actx, const struct token_error *err)
static bool parse_device_authz(struct async_ctx *actx, struct device_authz *authz)
static void free_token_error(struct token_error *err)
Definition oauth-curl.c:168

References actx_error, CHECK_GETINFO, err(), fb(), free_token_error(), libpq_gettext, parse_device_authz(), parse_token_error(), and record_token_error().

Referenced by pg_fe_run_oauth_flow_impl().

◆ finish_discovery()

static bool finish_discovery ( struct async_ctx actx)
static

Definition at line 2166 of file oauth-curl.c.

2167{
2168 long response_code;
2169
2170 /*----
2171 * Now check the response. OIDC Discovery 1.0 is pretty strict:
2172 *
2173 * A successful response MUST use the 200 OK HTTP status code and
2174 * return a JSON object using the application/json content type that
2175 * contains a set of Claims as its members that are a subset of the
2176 * Metadata values defined in Section 3.
2177 *
2178 * Compared to standard HTTP semantics, this makes life easy -- we don't
2179 * need to worry about redirections (which would call the Issuer host
2180 * validation into question), or non-authoritative responses, or any other
2181 * complications.
2182 */
2184
2185 if (response_code != 200)
2186 {
2187 actx_error(actx, "unexpected response code %ld", response_code);
2188 return false;
2189 }
2190
2191 /*
2192 * Pull the fields we care about from the document.
2193 */
2194 actx->errctx = libpq_gettext("failed to parse OpenID discovery document");
2195 if (!parse_provider(actx, &actx->provider))
2196 return false; /* error message already set */
2197
2198 /*
2199 * Fill in any defaults for OPTIONAL/RECOMMENDED fields we care about.
2200 */
2201 if (!actx->provider.grant_types_supported)
2202 {
2203 /*
2204 * Per Section 3, the default is ["authorization_code", "implicit"].
2205 */
2206 struct curl_slist *temp = actx->provider.grant_types_supported;
2207
2208 temp = curl_slist_append(temp, "authorization_code");
2209 if (temp)
2210 {
2211 temp = curl_slist_append(temp, "implicit");
2212 }
2213
2214 if (!temp)
2215 {
2216 actx_error(actx, "out of memory");
2217 return false;
2218 }
2219
2220 actx->provider.grant_types_supported = temp;
2221 }
2222
2223 return true;
2224}
static bool parse_provider(struct async_ctx *actx, struct provider *provider)
Definition oauth-curl.c:958

References actx_error, CHECK_GETINFO, fb(), libpq_gettext, and parse_provider().

Referenced by pg_fe_run_oauth_flow_impl().

◆ finish_token_request()

static bool finish_token_request ( struct async_ctx actx,
struct token tok 
)
static

Definition at line 2537 of file oauth-curl.c.

2538{
2539 long response_code;
2540
2542
2543 /*
2544 * Per RFC 6749, Section 5, a successful response uses 200 OK.
2545 */
2546 if (response_code == 200)
2547 {
2548 actx->errctx = libpq_gettext("failed to parse access token response");
2550 return false; /* error message already set */
2551
2552 return true;
2553 }
2554
2555 /*
2556 * An error response uses either 400 Bad Request or 401 Unauthorized.
2557 * There are references online to implementations using 403 for error
2558 * return which would violate the specification. For now we stick to the
2559 * specification but we might have to revisit this.
2560 */
2561 if (response_code == 400 || response_code == 401)
2562 {
2563 if (!parse_token_error(actx, &tok->err))
2564 return false;
2565
2566 return true;
2567 }
2568
2569 /* Any other response codes are considered invalid */
2570 actx_error(actx, "unexpected response code %ld", response_code);
2571 return false;
2572}
static bool parse_access_token(struct async_ctx *actx, struct token *tok)

References actx_error, CHECK_GETINFO, fb(), libpq_gettext, parse_access_token(), and parse_token_error().

Referenced by handle_token_response().

◆ free_async_ctx()

static void free_async_ctx ( struct async_ctx actx)
static

Definition at line 291 of file oauth-curl.c.

292{
293 /*
294 * In general, none of the error cases below should ever happen if we have
295 * no bugs above. But if we do hit them, surfacing those errors somehow
296 * might be the only way to have a chance to debug them.
297 *
298 * Print them as warnings to stderr, following the example of similar
299 * situations in fe-secure-openssl.c and fe-connect.c.
300 */
301
302 if (actx->curlm && actx->curl)
303 {
305
306 if (err)
308 libpq_gettext("WARNING: libcurl easy handle removal failed: %s\n"),
310 }
311
312 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 curl_easy_cleanup(actx->curl);
320 }
321
322 if (actx->curlm)
323 {
325
326 if (err)
328 libpq_gettext("WARNING: libcurl multi handle cleanup failed: %s\n"),
330 }
331
332 free_provider(&actx->provider);
333 free_device_authz(&actx->authz);
334
335 curl_slist_free_all(actx->headers);
336 termPQExpBuffer(&actx->work_data);
337 termPQExpBuffer(&actx->errbuf);
338
339 if (actx->mux != PGINVALID_SOCKET)
340 close(actx->mux);
341 if (actx->timerfd >= 0)
342 close(actx->timerfd);
343
344 free(actx->client_id);
345 free(actx->client_secret);
346 free(actx->ca_file);
347
348 free(actx);
349}
#define close(a)
Definition win32.h:12
static void free_provider(struct provider *provider)
Definition oauth-curl.c:115
static void free_device_authz(struct device_authz *authz)
Definition oauth-curl.c:143
#define PGINVALID_SOCKET
Definition port.h:31

References close, err(), fb(), fprintf, free, free_device_authz(), free_provider(), libpq_gettext, PGINVALID_SOCKET, and termPQExpBuffer().

Referenced by pg_fe_cleanup_oauth_flow().

◆ free_device_authz()

static void free_device_authz ( struct device_authz authz)
static

◆ free_provider()

◆ free_token()

static void free_token ( struct token tok)
static

Definition at line 194 of file oauth-curl.c.

195{
196 free(tok->access_token);
197 free(tok->token_type);
198 free_token_error(&tok->err);
199}

References fb(), free, and free_token_error().

Referenced by handle_token_response().

◆ free_token_error()

static void free_token_error ( struct token_error err)
static

Definition at line 168 of file oauth-curl.c.

169{
170 free(err->error);
171 free(err->error_description);
172}

References err(), and free.

Referenced by finish_device_authz(), and free_token().

◆ handle_token_response()

static bool handle_token_response ( struct async_ctx actx,
char **  token 
)
static

Definition at line 2584 of file oauth-curl.c.

2585{
2586 bool success = false;
2587 struct token tok = {0};
2588 const struct token_error *err;
2589
2591 goto token_cleanup;
2592
2593 /* A successful token request gives either a token or an in-band error. */
2594 Assert(tok.access_token || tok.err.error);
2595
2596 if (tok.access_token)
2597 {
2599 tok.access_token = NULL;
2600
2601 success = true;
2602 goto token_cleanup;
2603 }
2604
2605 /*
2606 * authorization_pending and slow_down are the only acceptable errors;
2607 * anything else and we bail. These are defined in RFC 8628, Sec. 3.5.
2608 */
2609 err = &tok.err;
2610 if (strcmp(err->error, "authorization_pending") != 0 &&
2611 strcmp(err->error, "slow_down") != 0)
2612 {
2614 goto token_cleanup;
2615 }
2616
2617 /*
2618 * A slow_down error requires us to permanently increase our retry
2619 * interval by five seconds.
2620 */
2621 if (strcmp(err->error, "slow_down") == 0)
2622 {
2623 int prev_interval = actx->authz.interval;
2624
2625 actx->authz.interval += 5;
2626 if (actx->authz.interval < prev_interval)
2627 {
2628 actx_error(actx, "slow_down interval overflow");
2629 goto token_cleanup;
2630 }
2631 }
2632
2633 success = true;
2634
2636 free_token(&tok);
2637 return success;
2638}
static bool finish_token_request(struct async_ctx *actx, struct token *tok)
static void free_token(struct token *tok)
Definition oauth-curl.c:194
char * access_token
Definition oauth-curl.c:186

References token::access_token, actx_error, Assert, err(), fb(), finish_token_request(), free_token(), record_token_error(), and success.

Referenced by pg_fe_run_oauth_flow_impl().

◆ initialize_curl()

static bool initialize_curl ( PGoauthBearerRequestV2 req)
static

Definition at line 2690 of file oauth-curl.c.

2691{
2692 /*
2693 * Don't let the compiler play tricks with this variable. In the
2694 * HAVE_THREADSAFE_CURL_GLOBAL_INIT case, we don't care if two threads
2695 * enter simultaneously, but we do care if this gets set transiently to
2696 * PG_BOOL_YES/NO in cases where that's not the final answer.
2697 */
2699#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
2701#endif
2702
2703#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2704
2705 /*
2706 * Lock around the whole function. If a libpq client performs its own work
2707 * with libcurl, it must either ensure that Curl is initialized safely
2708 * before calling us (in which case our call will be a no-op), or else it
2709 * must guard its own calls to curl_global_init() with a registered
2710 * threadlock handler. See PQregisterThreadLock().
2711 */
2712 pglock_thread();
2713#endif
2714
2715 /*
2716 * Skip initialization if we've already done it. (Curl tracks the number
2717 * of calls; there's no point in incrementing the counter every time we
2718 * connect.)
2719 */
2721 goto done;
2722 else if (init_successful == PG_BOOL_NO)
2723 {
2724 req->error = libpq_gettext("curl_global_init previously failed during OAuth setup");
2725 goto done;
2726 }
2727
2728 /*
2729 * We know we've already initialized Winsock by this point (see
2730 * pqMakeEmptyPGconn()), so we should be able to safely skip that bit. But
2731 * we have to tell libcurl to initialize everything else, because other
2732 * pieces of our client executable may already be using libcurl for their
2733 * own purposes. If we initialize libcurl with only a subset of its
2734 * features, we could break those other clients nondeterministically, and
2735 * that would probably be a nightmare to debug.
2736 *
2737 * If some other part of the program has already called this, it's a
2738 * no-op.
2739 */
2741 {
2742 req->error = libpq_gettext("curl_global_init failed during OAuth setup");
2744 goto done;
2745 }
2746
2747#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
2748
2749 /*
2750 * If we determined at configure time that the Curl installation is
2751 * thread-safe, our job here is much easier. We simply initialize above
2752 * without any locking (concurrent or duplicated calls are fine in that
2753 * situation), then double-check to make sure the runtime setting agrees,
2754 * to try to catch silent downgrades.
2755 */
2757 if (!(info->features & CURL_VERSION_THREADSAFE))
2758 {
2759 /*
2760 * In a downgrade situation, the damage is already done. Curl global
2761 * state may be corrupted. Be noisy.
2762 */
2763 req->error = libpq_gettext("libcurl is no longer thread-safe\n"
2764 "\tCurl initialization was reported thread-safe when libpq\n"
2765 "\twas compiled, but the currently installed version of\n"
2766 "\tlibcurl reports that it is not. Recompile libpq against\n"
2767 "\tthe installed version of libcurl.");
2769 goto done;
2770 }
2771#endif
2772
2774
2775done:
2776#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2778#endif
2779 return (init_successful == PG_BOOL_YES);
2780}
PGTernaryBool
Definition oauth-utils.h:32
@ PG_BOOL_YES
Definition oauth-utils.h:34
@ PG_BOOL_NO
Definition oauth-utils.h:35
@ PG_BOOL_UNKNOWN
Definition oauth-utils.h:33
#define pglock_thread()
Definition oauth-utils.h:49
#define pgunlock_thread()
Definition oauth-utils.h:50

References fb(), libpq_gettext, PG_BOOL_NO, PG_BOOL_UNKNOWN, PG_BOOL_YES, pglock_thread, and pgunlock_thread.

Referenced by pg_start_oauthbearer().

◆ oauth_json_array_end()

static JsonParseErrorType oauth_json_array_end ( void state)
static

Definition at line 684 of file oauth-curl.c.

685{
686 struct oauth_parse *ctx = state;
687
688 if (ctx->active)
689 {
690 /*
691 * Clear the target (which should be an array inside the top-level
692 * object). For this to be safe, no target arrays can contain other
693 * arrays; we check for that in the array_start callback.
694 */
695 if (ctx->nested != 2 || ctx->active->type != JSON_TOKEN_ARRAY_START)
696 {
697 Assert(false);
699 "internal error: found unexpected array end while parsing field \"%s\"",
700 ctx->active->name);
702 }
703
704 ctx->active = NULL;
705 }
706
707 --ctx->nested;
708 return JSON_SUCCESS;
709}
@ JSON_SEM_ACTION_FAILED
Definition jsonapi.h:59
@ JSON_SUCCESS
Definition jsonapi.h:36
@ JSON_TOKEN_ARRAY_START
Definition jsonapi.h:24
#define oauth_parse_set_error_internal(ctx, fmt,...)
Definition oauth-curl.c:512
const char * name
Definition oauth-curl.c:481
JsonTokenType type
Definition oauth-curl.c:483
const struct json_field * active
Definition oauth-curl.c:506

References oauth_parse::active, Assert, fb(), JSON_SEM_ACTION_FAILED, JSON_SUCCESS, JSON_TOKEN_ARRAY_START, json_field::name, oauth_parse::nested, oauth_parse_set_error_internal, and json_field::type.

Referenced by parse_oauth_json().

◆ oauth_json_array_start()

static JsonParseErrorType oauth_json_array_start ( void state)
static

Definition at line 652 of file oauth-curl.c.

653{
654 struct oauth_parse *ctx = state;
655
656 if (!ctx->nested)
657 {
658 oauth_parse_set_error(ctx, "top-level element must be an object");
660 }
661
662 if (ctx->active)
663 {
665 /* The arrays we care about must not have arrays as values. */
666 || ctx->nested > 1)
667 {
670 }
671 }
672
673 ++ctx->nested;
675 {
676 oauth_parse_set_error(ctx, "JSON is too deeply nested");
678 }
679
680 return JSON_SUCCESS;
681}
static void report_type_mismatch(struct oauth_parse *ctx)
Definition oauth-curl.c:516
#define MAX_OAUTH_NESTING_LEVEL
Definition oauth-curl.c:89
#define oauth_parse_set_error(ctx, fmt,...)
Definition oauth-curl.c:509

References oauth_parse::active, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, JSON_TOKEN_ARRAY_START, MAX_OAUTH_NESTING_LEVEL, oauth_parse::nested, oauth_parse_set_error, report_type_mismatch(), and json_field::type.

Referenced by parse_oauth_json().

◆ oauth_json_object_end()

static JsonParseErrorType oauth_json_object_end ( void state)
static

Definition at line 629 of file oauth-curl.c.

630{
631 struct oauth_parse *ctx = state;
632
633 --ctx->nested;
634
635 /*
636 * All fields should be fully processed by the end of the top-level
637 * object.
638 */
639 if (!ctx->nested && ctx->active)
640 {
641 Assert(false);
643 "internal error: field \"%s\" still active at end of object",
644 ctx->active->name);
646 }
647
648 return JSON_SUCCESS;
649}

References oauth_parse::active, Assert, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, json_field::name, oauth_parse::nested, and oauth_parse_set_error_internal.

Referenced by parse_oauth_json().

◆ oauth_json_object_field_start()

static JsonParseErrorType oauth_json_object_field_start ( void state,
char name,
bool  isnull 
)
static

Definition at line 574 of file oauth-curl.c.

575{
576 struct oauth_parse *ctx = state;
577
578 /* We care only about the top-level fields. */
579 if (ctx->nested == 1)
580 {
581 const struct json_field *field = ctx->fields;
582
583 /*
584 * We should never start parsing a new field while a previous one is
585 * still active.
586 */
587 if (ctx->active)
588 {
589 Assert(false);
591 "internal error: started field \"%s\" before field \"%s\" was finished",
592 name, ctx->active->name);
594 }
595
596 while (field->name)
597 {
598 if (strcmp(name, field->name) == 0)
599 {
600 ctx->active = field;
601 break;
602 }
603
604 ++field;
605 }
606
607 /*
608 * We don't allow duplicate field names; error out if the target has
609 * already been set.
610 */
611 if (ctx->active)
612 {
613 field = ctx->active;
614
615 if ((field->type == JSON_TOKEN_ARRAY_START && *field->array)
616 || (field->type != JSON_TOKEN_ARRAY_START && *field->scalar))
617 {
618 oauth_parse_set_error(ctx, "field \"%s\" is duplicated",
619 field->name);
621 }
622 }
623 }
624
625 return JSON_SUCCESS;
626}
struct curl_slist ** array
Definition oauth-curl.c:489
char ** scalar
Definition oauth-curl.c:488
const struct json_field * fields
Definition oauth-curl.c:505
const char * name

References oauth_parse::active, json_field::array, Assert, fb(), oauth_parse::fields, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, JSON_TOKEN_ARRAY_START, name, json_field::name, oauth_parse::nested, oauth_parse_set_error, oauth_parse_set_error_internal, json_field::scalar, and json_field::type.

Referenced by parse_oauth_json().

◆ oauth_json_object_start()

static JsonParseErrorType oauth_json_object_start ( void state)
static

Definition at line 549 of file oauth-curl.c.

550{
551 struct oauth_parse *ctx = state;
552
553 if (ctx->active)
554 {
555 /*
556 * Currently, none of the fields we're interested in can be or contain
557 * objects, so we can reject this case outright.
558 */
561 }
562
563 ++ctx->nested;
565 {
566 oauth_parse_set_error(ctx, "JSON is too deeply nested");
568 }
569
570 return JSON_SUCCESS;
571}

References oauth_parse::active, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, MAX_OAUTH_NESTING_LEVEL, oauth_parse::nested, oauth_parse_set_error, and report_type_mismatch().

Referenced by parse_oauth_json().

◆ oauth_json_scalar()

static JsonParseErrorType oauth_json_scalar ( void state,
char token,
JsonTokenType  type 
)
static

Definition at line 712 of file oauth-curl.c.

713{
714 struct oauth_parse *ctx = state;
715
716 if (!ctx->nested)
717 {
718 oauth_parse_set_error(ctx, "top-level element must be an object");
720 }
721
722 if (ctx->active)
723 {
724 const struct json_field *field = ctx->active;
725 JsonTokenType expected = field->type;
726
727 /* Make sure this matches what the active field expects. */
729 {
730 /* Are we actually inside an array? */
731 if (ctx->nested < 2)
732 {
735 }
736
737 /* Currently, arrays can only contain strings. */
739 }
740
741 if (type != expected)
742 {
745 }
746
747 if (field->type != JSON_TOKEN_ARRAY_START)
748 {
749 /* Ensure that we're parsing the top-level keys... */
750 if (ctx->nested != 1)
751 {
752 Assert(false);
754 "internal error: scalar target found at nesting level %d",
755 ctx->nested);
757 }
758
759 /* ...and that a result has not already been set. */
760 if (*field->scalar)
761 {
762 Assert(false);
764 "internal error: scalar field \"%s\" would be assigned twice",
765 ctx->active->name);
767 }
768
769 *field->scalar = strdup(token);
770 if (!*field->scalar)
771 return JSON_OUT_OF_MEMORY;
772
773 ctx->active = NULL;
774
775 return JSON_SUCCESS;
776 }
777 else
778 {
779 struct curl_slist *temp;
780
781 /* The target array should be inside the top-level object. */
782 if (ctx->nested != 2)
783 {
784 Assert(false);
786 "internal error: array member found at nesting level %d",
787 ctx->nested);
789 }
790
791 /* Note that curl_slist_append() makes a copy of the token. */
792 temp = curl_slist_append(*field->array, token);
793 if (!temp)
794 return JSON_OUT_OF_MEMORY;
795
796 *field->array = temp;
797 }
798 }
799 else
800 {
801 /* otherwise we just ignore it */
802 }
803
804 return JSON_SUCCESS;
805}
@ JSON_OUT_OF_MEMORY
Definition jsonapi.h:52
JsonTokenType
Definition jsonapi.h:18
@ JSON_TOKEN_STRING
Definition jsonapi.h:20

References oauth_parse::active, json_field::array, Assert, fb(), JSON_OUT_OF_MEMORY, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, JSON_TOKEN_ARRAY_START, JSON_TOKEN_STRING, json_field::name, oauth_parse::nested, oauth_parse_set_error, oauth_parse_set_error_internal, report_type_mismatch(), json_field::scalar, type, and json_field::type.

Referenced by parse_oauth_json().

◆ parse_access_token()

static bool parse_access_token ( struct async_ctx actx,
struct token tok 
)
static

Definition at line 1183 of file oauth-curl.c.

1184{
1185 struct json_field fields[] = {
1186 {"access_token", JSON_TOKEN_STRING, {&tok->access_token}, PG_OAUTH_REQUIRED},
1187 {"token_type", JSON_TOKEN_STRING, {&tok->token_type}, PG_OAUTH_REQUIRED},
1188
1189 /*---
1190 * We currently have no use for the following OPTIONAL fields:
1191 *
1192 * - expires_in: This will be important for maintaining a token cache,
1193 * but we do not yet implement one.
1194 *
1195 * - refresh_token: Ditto.
1196 *
1197 * - scope: This is only sent when the authorization server sees fit to
1198 * change our scope request. It's not clear what we should do
1199 * about this; either it's been done as a matter of policy, or
1200 * the user has explicitly denied part of the authorization,
1201 * and either way the server-side validator is in a better
1202 * place to complain if the change isn't acceptable.
1203 */
1204
1205 {0},
1206 };
1207
1208 return parse_oauth_json(actx, fields);
1209}
static bool parse_oauth_json(struct async_ctx *actx, const struct json_field *fields)
Definition oauth-curl.c:871
#define PG_OAUTH_REQUIRED
Definition oauth-curl.c:496

References fb(), JSON_TOKEN_STRING, parse_oauth_json(), and PG_OAUTH_REQUIRED.

Referenced by finish_token_request().

◆ parse_device_authz()

static bool parse_device_authz ( struct async_ctx actx,
struct device_authz authz 
)
static

Definition at line 1069 of file oauth-curl.c.

1070{
1071 struct json_field fields[] = {
1072 {"device_code", JSON_TOKEN_STRING, {&authz->device_code}, PG_OAUTH_REQUIRED},
1073 {"user_code", JSON_TOKEN_STRING, {&authz->user_code}, PG_OAUTH_REQUIRED},
1074 {"verification_uri", JSON_TOKEN_STRING, {&authz->verification_uri}, PG_OAUTH_REQUIRED},
1075 {"expires_in", JSON_TOKEN_NUMBER, {&authz->expires_in_str}, PG_OAUTH_REQUIRED},
1076
1077 /*
1078 * Some services (Google, Azure) spell verification_uri differently.
1079 * We accept either.
1080 */
1081 {"verification_url", JSON_TOKEN_STRING, {&authz->verification_uri}, PG_OAUTH_REQUIRED},
1082
1083 /*
1084 * There is no evidence of verification_uri_complete being spelled
1085 * with "url" instead with any service provider, so only support
1086 * "uri".
1087 */
1088 {"verification_uri_complete", JSON_TOKEN_STRING, {&authz->verification_uri_complete}, PG_OAUTH_OPTIONAL},
1089 {"interval", JSON_TOKEN_NUMBER, {&authz->interval_str}, PG_OAUTH_OPTIONAL},
1090
1091 {0},
1092 };
1093
1094 if (!parse_oauth_json(actx, fields))
1095 return false;
1096
1097 /*
1098 * Parse our numeric fields. Lexing has already completed by this time, so
1099 * we at least know they're valid JSON numbers.
1100 */
1101 if (authz->interval_str)
1102 authz->interval = parse_interval(actx, authz->interval_str);
1103 else
1104 {
1105 /*
1106 * RFC 8628 specifies 5 seconds as the default value if the server
1107 * doesn't provide an interval.
1108 */
1109 authz->interval = 5;
1110 }
1111
1112 Assert(authz->expires_in_str); /* ensured by parse_oauth_json() */
1114
1115 return true;
1116}
@ JSON_TOKEN_NUMBER
Definition jsonapi.h:21
static int parse_interval(struct async_ctx *actx, const char *interval_str)
#define PG_OAUTH_OPTIONAL
Definition oauth-curl.c:497
static int parse_expires_in(struct async_ctx *actx, const char *expires_in_str)

References Assert, device_authz::device_code, device_authz::expires_in, device_authz::expires_in_str, fb(), device_authz::interval, device_authz::interval_str, JSON_TOKEN_NUMBER, JSON_TOKEN_STRING, parse_expires_in(), parse_interval(), parse_oauth_json(), PG_OAUTH_OPTIONAL, PG_OAUTH_REQUIRED, device_authz::user_code, device_authz::verification_uri, and device_authz::verification_uri_complete.

Referenced by finish_device_authz().

◆ parse_expires_in()

static int parse_expires_in ( struct async_ctx actx,
const char expires_in_str 
)
static

Definition at line 1050 of file oauth-curl.c.

1051{
1052 double parsed;
1053
1054 parsed = parse_json_number(expires_in_str);
1055 parsed = floor(parsed);
1056
1057 if (parsed >= INT_MAX)
1058 return INT_MAX;
1059 else if (parsed <= INT_MIN)
1060 return INT_MIN;
1061
1062 return parsed;
1063}
static double parse_json_number(const char *s)
Definition oauth-curl.c:989

References fb(), and parse_json_number().

Referenced by parse_device_authz().

◆ parse_interval()

static int parse_interval ( struct async_ctx actx,
const char interval_str 
)
static

Definition at line 1024 of file oauth-curl.c.

1025{
1026 double parsed;
1027
1028 parsed = parse_json_number(interval_str);
1029 parsed = ceil(parsed);
1030
1031 if (parsed < 1)
1032 return (actx->debug_flags & OAUTHDEBUG_UNSAFE_DOS_ENDPOINT) ? 0 : 1;
1033
1034 else if (parsed >= INT_MAX)
1035 return INT_MAX;
1036
1037 return parsed;
1038}
#define OAUTHDEBUG_UNSAFE_DOS_ENDPOINT
Definition oauth-debug.h:42

References fb(), OAUTHDEBUG_UNSAFE_DOS_ENDPOINT, and parse_json_number().

Referenced by parse_device_authz().

◆ parse_json_number()

static double parse_json_number ( const char s)
static

Definition at line 989 of file oauth-curl.c.

990{
991 double parsed;
992 int cnt;
993
994 /*
995 * The JSON lexer has already validated the number, which is stricter than
996 * the %f format, so we should be good to use sscanf().
997 */
998 cnt = sscanf(s, "%lf", &parsed);
999
1000 if (cnt != 1)
1001 {
1002 /*
1003 * Either the lexer screwed up or our assumption above isn't true, and
1004 * either way a developer needs to take a look.
1005 */
1006 Assert(false);
1007 return 0;
1008 }
1009
1010 return parsed;
1011}

References Assert, and fb().

Referenced by parse_expires_in(), and parse_interval().

◆ parse_oauth_json()

static bool parse_oauth_json ( struct async_ctx actx,
const struct json_field fields 
)
static

Definition at line 871 of file oauth-curl.c.

872{
873 PQExpBuffer resp = &actx->work_data;
874 JsonLexContext lex = {0};
875 JsonSemAction sem = {0};
877 struct oauth_parse ctx = {0};
878 bool success = false;
879
880 if (!check_content_type(actx, "application/json"))
881 return false;
882
883 if (strlen(resp->data) != resp->len)
884 {
885 actx_error(actx, "response contains embedded NULLs");
886 return false;
887 }
888
889 /*
890 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
891 * that up front.
892 */
893 if (pg_encoding_verifymbstr(PG_UTF8, resp->data, resp->len) != resp->len)
894 {
895 actx_error(actx, "response is not valid UTF-8");
896 return false;
897 }
898
899 makeJsonLexContextCstringLen(&lex, resp->data, resp->len, PG_UTF8, true);
900 setJsonLexContextOwnsTokens(&lex, true); /* must not leak on error */
901
902 ctx.errbuf = &actx->errbuf;
903 ctx.fields = fields;
904 sem.semstate = &ctx;
905
912
913 err = pg_parse_json(&lex, &sem);
914
915 if (err != JSON_SUCCESS)
916 {
917 /*
918 * For JSON_SEM_ACTION_FAILED, we've already written the error
919 * message. Other errors come directly from pg_parse_json(), already
920 * translated.
921 */
924
925 goto cleanup;
926 }
927
928 /* Check all required fields. */
929 while (fields->name)
930 {
931 if (fields->required
932 && !*fields->scalar
933 && !*fields->array)
934 {
935 actx_error(actx, "field \"%s\" is missing", fields->name);
936 goto cleanup;
937 }
938
939 fields++;
940 }
941
942 success = true;
943
944cleanup:
945 freeJsonLexContext(&lex);
946 return success;
947}
JsonParseErrorType pg_parse_json(JsonLexContext *lex, const JsonSemAction *sem)
Definition jsonapi.c:744
JsonLexContext * makeJsonLexContextCstringLen(JsonLexContext *lex, const char *json, size_t len, int encoding, bool need_escapes)
Definition jsonapi.c:392
void setJsonLexContextOwnsTokens(JsonLexContext *lex, bool owned_by_context)
Definition jsonapi.c:542
char * json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
Definition jsonapi.c:2453
void freeJsonLexContext(JsonLexContext *lex)
Definition jsonapi.c:687
JsonParseErrorType
Definition jsonapi.h:35
#define PG_UTF8
Definition mbprint.c:43
static JsonParseErrorType oauth_json_array_end(void *state)
Definition oauth-curl.c:684
static JsonParseErrorType oauth_json_object_field_start(void *state, char *name, bool isnull)
Definition oauth-curl.c:574
static JsonParseErrorType oauth_json_scalar(void *state, char *token, JsonTokenType type)
Definition oauth-curl.c:712
static JsonParseErrorType oauth_json_array_start(void *state)
Definition oauth-curl.c:652
static JsonParseErrorType oauth_json_object_end(void *state)
Definition oauth-curl.c:629
static bool check_content_type(struct async_ctx *actx, const char *type)
Definition oauth-curl.c:812
static JsonParseErrorType oauth_json_object_start(void *state)
Definition oauth-curl.c:549
json_struct_action array_end
Definition jsonapi.h:157
json_struct_action object_start
Definition jsonapi.h:154
json_ofield_action object_field_start
Definition jsonapi.h:158
json_scalar_action scalar
Definition jsonapi.h:162
void * semstate
Definition jsonapi.h:153
json_struct_action array_start
Definition jsonapi.h:156
json_struct_action object_end
Definition jsonapi.h:155
bool required
Definition oauth-curl.c:492
PQExpBuffer errbuf
Definition oauth-curl.c:502
static JsonSemAction sem
int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len)
Definition wchar.c:2001

References actx_error, actx_error_str, json_field::array, JsonSemAction::array_end, JsonSemAction::array_start, check_content_type(), cleanup(), err(), oauth_parse::errbuf, fb(), oauth_parse::fields, freeJsonLexContext(), json_errdetail(), JSON_SEM_ACTION_FAILED, JSON_SUCCESS, makeJsonLexContextCstringLen(), json_field::name, oauth_json_array_end(), oauth_json_array_start(), oauth_json_object_end(), oauth_json_object_field_start(), oauth_json_object_start(), oauth_json_scalar(), JsonSemAction::object_end, JsonSemAction::object_field_start, JsonSemAction::object_start, pg_encoding_verifymbstr(), pg_parse_json(), PG_UTF8, json_field::required, JsonSemAction::scalar, json_field::scalar, sem, JsonSemAction::semstate, setJsonLexContextOwnsTokens(), and success.

Referenced by parse_access_token(), parse_device_authz(), parse_provider(), and parse_token_error().

◆ parse_provider()

static bool parse_provider ( struct async_ctx actx,
struct provider provider 
)
static

Definition at line 958 of file oauth-curl.c.

959{
960 struct json_field fields[] = {
963
964 /*----
965 * The following fields are technically REQUIRED, but we don't use
966 * them anywhere yet:
967 *
968 * - jwks_uri
969 * - response_types_supported
970 * - subject_types_supported
971 * - id_token_signing_alg_values_supported
972 */
973
974 {"device_authorization_endpoint", JSON_TOKEN_STRING, {&provider->device_authorization_endpoint}, PG_OAUTH_OPTIONAL},
975 {"grant_types_supported", JSON_TOKEN_ARRAY_START, {.array = &provider->grant_types_supported}, PG_OAUTH_OPTIONAL},
976
977 {0},
978 };
979
980 return parse_oauth_json(actx, fields);
981}

References provider::device_authorization_endpoint, fb(), provider::grant_types_supported, provider::issuer, JSON_TOKEN_ARRAY_START, JSON_TOKEN_STRING, parse_oauth_json(), PG_OAUTH_OPTIONAL, PG_OAUTH_REQUIRED, and provider::token_endpoint.

Referenced by finish_discovery().

◆ parse_token_error()

static bool parse_token_error ( struct async_ctx actx,
struct token_error err 
)
static

Definition at line 1123 of file oauth-curl.c.

1124{
1125 bool result;
1126 struct json_field fields[] = {
1127 {"error", JSON_TOKEN_STRING, {&err->error}, PG_OAUTH_REQUIRED},
1128
1129 {"error_description", JSON_TOKEN_STRING, {&err->error_description}, PG_OAUTH_OPTIONAL},
1130
1131 {0},
1132 };
1133
1134 result = parse_oauth_json(actx, fields);
1135
1136 /*
1137 * Since token errors are parsed during other active error paths, only
1138 * override the errctx if parsing explicitly fails.
1139 */
1140 if (!result)
1141 actx->errctx = libpq_gettext("failed to parse token error response");
1142
1143 return result;
1144}
uint32 result

References err(), fb(), JSON_TOKEN_STRING, libpq_gettext, parse_oauth_json(), PG_OAUTH_OPTIONAL, PG_OAUTH_REQUIRED, and result.

Referenced by finish_device_authz(), and finish_token_request().

◆ pg_fe_cleanup_oauth_flow()

static void pg_fe_cleanup_oauth_flow ( PGconn conn,
PGoauthBearerRequest request 
)
static

Definition at line 355 of file oauth-curl.c.

356{
357 struct async_ctx *actx = request->user;
358
359 /* request->cleanup is only set after actx has been allocated. */
360 Assert(actx);
361
363 request->user = NULL;
364
365 /* libpq has made its own copy of the token; clear ours now. */
366 if (request->token)
367 {
368 explicit_bzero(request->token, strlen(request->token));
369 free(request->token);
370 request->token = NULL;
371 }
372}
static void free_async_ctx(struct async_ctx *actx)
Definition oauth-curl.c:291
void explicit_bzero(void *buf, size_t len)

References Assert, explicit_bzero(), fb(), free, and free_async_ctx().

Referenced by pg_start_oauthbearer().

◆ pg_fe_run_oauth_flow()

static PostgresPollingStatusType pg_fe_run_oauth_flow ( PGconn conn,
struct PGoauthBearerRequest request,
int altsock 
)
static

Definition at line 2996 of file oauth-curl.c.

2998{
3000 struct async_ctx *actx = request->user;
3001#ifndef WIN32
3003 bool sigpipe_pending;
3004 bool masked;
3005
3006 /*---
3007 * Ignore SIGPIPE on this thread during all Curl processing.
3008 *
3009 * Because we support multiple threads, we have to set up libcurl with
3010 * CURLOPT_NOSIGNAL, which disables its default global handling of
3011 * SIGPIPE. From the Curl docs:
3012 *
3013 * libcurl makes an effort to never cause such SIGPIPE signals to
3014 * trigger, but some operating systems have no way to avoid them and
3015 * even on those that have there are some corner cases when they may
3016 * still happen, contrary to our desire.
3017 *
3018 * Note that libcurl is also at the mercy of its DNS resolution and SSL
3019 * libraries; if any of them forget a MSG_NOSIGNAL then we're in trouble.
3020 * Modern platforms and libraries seem to get it right, so this is a
3021 * difficult corner case to exercise in practice, and unfortunately it's
3022 * not really clear whether it's necessary in all cases.
3023 */
3024 masked = (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0);
3025#endif
3026
3029 altsock);
3030
3031 /*
3032 * To assist with finding bugs in comb_multiplexer() and
3033 * drain_timer_events(), when we're in debug mode, track the total number
3034 * of calls to this function and print that at the end of the flow.
3035 */
3036 if (actx->debug_flags & OAUTHDEBUG_CALL_COUNT)
3037 {
3038 actx->dbg_num_calls++;
3040 fprintf(stderr, "[libpq] total number of polls: %d\n",
3041 actx->dbg_num_calls);
3042 }
3043
3044#ifndef WIN32
3045 if (masked)
3046 {
3047 /*
3048 * Undo the SIGPIPE mask. Assume we may have gotten EPIPE (we have no
3049 * way of knowing at this level).
3050 */
3051 pq_reset_sigpipe(&osigset, sigpipe_pending, true /* EPIPE, maybe */ );
3052 }
3053#endif
3054
3055 return result;
3056}
PostgresPollingStatusType
Definition libpq-fe.h:120
static PostgresPollingStatusType pg_fe_run_oauth_flow_impl(PGconn *conn, PGoauthBearerRequestV2 *request, int *altsock)
#define OAUTHDEBUG_CALL_COUNT
Definition oauth-debug.h:47
void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
Definition oauth-utils.c:94
PGconn * conn
Definition streamutil.c:52

References conn, fb(), fprintf, OAUTHDEBUG_CALL_COUNT, pg_fe_run_oauth_flow_impl(), PGRES_POLLING_FAILED, PGRES_POLLING_OK, pq_block_sigpipe(), pq_reset_sigpipe(), and result.

Referenced by pg_start_oauthbearer().

◆ pg_fe_run_oauth_flow_impl()

static PostgresPollingStatusType pg_fe_run_oauth_flow_impl ( PGconn conn,
PGoauthBearerRequestV2 request,
int altsock 
)
static

Definition at line 2797 of file oauth-curl.c.

2799{
2800 struct async_ctx *actx = request->v1.user;
2801 char *oauth_token = NULL;
2802
2803 do
2804 {
2805 /* By default, the multiplexer is the altsock. Reassign as desired. */
2806 *altsock = actx->mux;
2807
2808 switch (actx->step)
2809 {
2810 case OAUTH_STEP_INIT:
2811 break;
2812
2816 {
2818
2819 /*
2820 * Clear any expired timeout before calling back into
2821 * Curl. Curl is not guaranteed to do this for us, because
2822 * its API expects us to use single-shot (i.e.
2823 * edge-triggered) timeouts, and ours are level-triggered
2824 * via the mux.
2825 *
2826 * This can't be combined with the comb_multiplexer() call
2827 * below: we might accidentally clear a short timeout that
2828 * was both set and expired during the call to
2829 * drive_request().
2830 */
2832 goto error_return;
2833
2834 /* Move the request forward. */
2835 status = drive_request(actx);
2836
2837 if (status == PGRES_POLLING_FAILED)
2838 goto error_return;
2839 else if (status == PGRES_POLLING_OK)
2840 break; /* done! */
2841
2842 /*
2843 * This request is still running.
2844 *
2845 * Make sure that stale events don't cause us to come back
2846 * early. (Currently, this can occur only with kqueue.) If
2847 * this is forgotten, the multiplexer can get stuck in a
2848 * signaled state and we'll burn CPU cycles pointlessly.
2849 */
2850 if (!comb_multiplexer(actx))
2851 goto error_return;
2852
2853 return status;
2854 }
2855
2857 {
2858 bool expired;
2859
2860 /*
2861 * The client application is supposed to wait until our
2862 * timer expires before calling PQconnectPoll() again, but
2863 * that might not happen. To avoid sending a token request
2864 * early, check the timer before continuing.
2865 */
2867 goto error_return;
2868
2869 if (!expired)
2870 {
2871 *altsock = actx->timerfd;
2872 return PGRES_POLLING_READING;
2873 }
2874
2875 break;
2876 }
2877 }
2878
2879 /*
2880 * Each case here must ensure that actx->running is set while we're
2881 * waiting on some asynchronous work. Most cases rely on
2882 * start_request() to do that for them.
2883 */
2884 switch (actx->step)
2885 {
2886 case OAUTH_STEP_INIT:
2887 actx->errctx = libpq_gettext("failed to fetch OpenID discovery document");
2888 if (!start_discovery(actx, actx->discovery_uri))
2889 goto error_return;
2890
2891 actx->step = OAUTH_STEP_DISCOVERY;
2892 break;
2893
2895 if (!finish_discovery(actx))
2896 goto error_return;
2897
2898 if (!check_issuer(actx, conn))
2899 goto error_return;
2900
2901 actx->errctx = libpq_gettext("cannot run OAuth device authorization");
2903 goto error_return;
2904
2905 actx->errctx = libpq_gettext("failed to obtain device authorization");
2907 goto error_return;
2908
2910 break;
2911
2914 goto error_return;
2915
2916 actx->errctx = libpq_gettext("failed to obtain access token");
2918 goto error_return;
2919
2921 break;
2922
2924 if (!handle_token_response(actx, &oauth_token))
2925 goto error_return;
2926
2927 /*
2928 * Hook any oauth_token into the request struct immediately so
2929 * that the allocation isn't lost in case of an error.
2930 */
2931 request->v1.token = oauth_token;
2932
2933 if (!actx->user_prompted)
2934 {
2935 /*
2936 * Now that we know the token endpoint isn't broken, give
2937 * the user the login instructions.
2938 */
2939 if (!prompt_user(actx, conn))
2940 goto error_return;
2941
2942 actx->user_prompted = true;
2943 }
2944
2945 if (oauth_token)
2946 break; /* done! */
2947
2948 /*
2949 * Wait for the required interval before issuing the next
2950 * request.
2951 */
2952 if (!set_timer(actx, actx->authz.interval * 1000))
2953 goto error_return;
2954
2955 /*
2956 * No Curl requests are running, so we can simplify by having
2957 * the client wait directly on the timerfd rather than the
2958 * multiplexer.
2959 */
2960 *altsock = actx->timerfd;
2961
2963 actx->running = 1;
2964 break;
2965
2967 actx->errctx = libpq_gettext("failed to obtain access token");
2969 goto error_return;
2970
2972 break;
2973 }
2974
2975 /*
2976 * The vast majority of the time, if we don't have a token at this
2977 * point, actx->running will be set. But there are some corner cases
2978 * where we can immediately loop back around; see start_request().
2979 */
2980 } while (!oauth_token && !actx->running);
2981
2982 /* If we've stored a token, we're done. Otherwise come back later. */
2983 return oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
2984
2987
2988 return PGRES_POLLING_FAILED;
2989}
static bool drain_timer_events(struct async_ctx *actx, bool *was_expired)
static bool start_token_request(struct async_ctx *actx, PGconn *conn)
static PostgresPollingStatusType drive_request(struct async_ctx *actx)
static bool start_device_authz(struct async_ctx *actx, PGconn *conn)
static bool prompt_user(struct async_ctx *actx, PGconn *conn)
static bool finish_discovery(struct async_ctx *actx)
static void append_actx_error(PGoauthBearerRequestV2 *req, struct async_ctx *actx)
Definition oauth-curl.c:379
static bool start_discovery(struct async_ctx *actx, const char *discovery_uri)
static bool finish_device_authz(struct async_ctx *actx)
static bool comb_multiplexer(struct async_ctx *actx)
static bool check_issuer(struct async_ctx *actx, PGconn *conn)
static bool handle_token_response(struct async_ctx *actx, char **token)
static bool check_for_device_flow(struct async_ctx *actx)
pgsocket mux
Definition oauth-curl.c:235

References append_actx_error(), check_for_device_flow(), check_issuer(), comb_multiplexer(), conn, drain_timer_events(), drive_request(), fb(), finish_device_authz(), finish_discovery(), handle_token_response(), libpq_gettext, async_ctx::mux, OAUTH_STEP_DEVICE_AUTHORIZATION, OAUTH_STEP_DISCOVERY, OAUTH_STEP_INIT, OAUTH_STEP_TOKEN_REQUEST, OAUTH_STEP_WAIT_INTERVAL, PGRES_POLLING_FAILED, PGRES_POLLING_OK, PGRES_POLLING_READING, prompt_user(), set_timer(), start_device_authz(), start_discovery(), and start_token_request().

Referenced by pg_fe_run_oauth_flow().

◆ pg_start_oauthbearer()

int pg_start_oauthbearer ( PGconn conn,
PGoauthBearerRequestV2 request 
)

Definition at line 3063 of file oauth-curl.c.

3064{
3065 struct async_ctx *actx;
3066 PQconninfoOption *conninfo = NULL;
3067
3069 return -1;
3070
3071 /*
3072 * Create our asynchronous state, and hook it into the upper-level OAuth
3073 * state immediately, so any failures below won't leak the context
3074 * allocation.
3075 */
3076 actx = calloc(1, sizeof(*actx));
3077 if (!actx)
3078 goto oom;
3079
3080 actx->mux = PGINVALID_SOCKET;
3081 actx->timerfd = -1;
3082
3083 /*
3084 * Now we have a valid (but still useless) actx, so we can fill in the
3085 * request object. From this point onward, failures will result in a call
3086 * to pg_fe_cleanup_oauth_flow(). Further cleanup logic belongs there.
3087 */
3088 request->v1.async = pg_fe_run_oauth_flow;
3089 request->v1.cleanup = pg_fe_cleanup_oauth_flow;
3090 request->v1.user = actx;
3091
3092 /*
3093 * Now finish filling in the actx.
3094 */
3095
3096 /* Parse debug flags from the environment. */
3097 actx->debug_flags = oauth_parse_debug_flags();
3098
3099 initPQExpBuffer(&actx->work_data);
3100 initPQExpBuffer(&actx->errbuf);
3101
3102 /* Pull relevant connection options. */
3103 conninfo = PQconninfo(conn);
3104 if (!conninfo)
3105 goto oom;
3106
3107 for (PQconninfoOption *opt = conninfo; opt->keyword; opt++)
3108 {
3109 if (!opt->val)
3110 continue; /* simplifies the strdup logic below */
3111
3112 if (strcmp(opt->keyword, "oauth_client_id") == 0)
3113 {
3114 actx->client_id = strdup(opt->val);
3115 if (!actx->client_id)
3116 goto oom;
3117 }
3118 else if (strcmp(opt->keyword, "oauth_client_secret") == 0)
3119 {
3120 actx->client_secret = strdup(opt->val);
3121 if (!actx->client_secret)
3122 goto oom;
3123 }
3124 else if (strcmp(opt->keyword, "oauth_ca_file") == 0)
3125 {
3126 actx->ca_file = strdup(opt->val);
3127 if (!actx->ca_file)
3128 goto oom;
3129 }
3130 }
3131
3132 PQconninfoFree(conninfo);
3133 conninfo = NULL; /* keeps `goto oom` safe */
3134
3135 actx->discovery_uri = request->v1.openid_configuration;
3136 actx->issuer_id = request->issuer;
3137 actx->scope = request->v1.scope;
3138
3139 Assert(actx->client_id); /* ensured by setup_oauth_parameters() */
3140 Assert(actx->issuer_id); /* ensured by setup_oauth_parameters() */
3141 Assert(actx->discovery_uri); /* ensured by oauth_exchange() */
3142
3143 if (!setup_multiplexer(actx))
3144 {
3146 return -1;
3147 }
3148
3150 {
3152 return -1;
3153 }
3154
3155 return 0;
3156
3157oom:
3158 if (conninfo)
3159 PQconninfoFree(conninfo);
3160
3161 request->error = libpq_gettext("out of memory");
3162 return -1;
3163}
PQconninfoOption * PQconninfo(PGconn *conn)
void PQconninfoFree(PQconninfoOption *connOptions)
static bool setup_multiplexer(struct async_ctx *actx)
static PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn, struct PGoauthBearerRequest *request, int *altsock)
static void pg_fe_cleanup_oauth_flow(PGconn *conn, PGoauthBearerRequest *request)
Definition oauth-curl.c:355
static bool initialize_curl(PGoauthBearerRequestV2 *req)
static bool setup_curl_handles(struct async_ctx *actx)
static uint32 oauth_parse_debug_flags(void)
Definition oauth-debug.h:79
#define calloc(a, b)

References append_actx_error(), Assert, calloc, conn, fb(), initialize_curl(), initPQExpBuffer(), _PQconninfoOption::keyword, libpq_gettext, oauth_parse_debug_flags(), pg_fe_cleanup_oauth_flow(), pg_fe_run_oauth_flow(), PGINVALID_SOCKET, PQconninfo(), PQconninfoFree(), setup_curl_handles(), and setup_multiplexer().

◆ prompt_user()

static bool prompt_user ( struct async_ctx actx,
PGconn conn 
)
static

Definition at line 2645 of file oauth-curl.c.

2646{
2647 int res;
2649 .verification_uri = actx->authz.verification_uri,
2650 .user_code = actx->authz.user_code,
2651 .verification_uri_complete = actx->authz.verification_uri_complete,
2652 .expires_in = actx->authz.expires_in,
2653 };
2655
2657
2658 if (!res)
2659 {
2660 /*
2661 * translator: The first %s is a URL for the user to visit in a
2662 * browser, and the second %s is a code to be copy-pasted there.
2663 */
2664 fprintf(stderr, libpq_gettext("Visit %s and enter the code: %s\n"),
2665 prompt.verification_uri, prompt.user_code);
2666 }
2667 else if (res < 0)
2668 {
2669 actx_error(actx, "device prompt failed");
2670 return false;
2671 }
2672
2673 return true;
2674}
PQauthDataHook_type PQgetAuthDataHook(void)
Definition fe-auth.c:1589
int(* PQauthDataHook_type)(PGauthData type, PGconn *conn, void *data)
Definition libpq-fe.h:847
@ PQAUTHDATA_PROMPT_OAUTH_DEVICE
Definition libpq-fe.h:200
const char * verification_uri
Definition libpq-fe.h:745

References actx_error, conn, fb(), fprintf, libpq_gettext, PQAUTHDATA_PROMPT_OAUTH_DEVICE, PQgetAuthDataHook(), and _PGpromptOAuthDevice::verification_uri.

Referenced by pg_fe_run_oauth_flow_impl().

◆ record_token_error()

static void record_token_error ( struct async_ctx actx,
const struct token_error err 
)
static

Definition at line 1151 of file oauth-curl.c.

1152{
1153 if (err->error_description)
1154 appendPQExpBuffer(&actx->errbuf, "%s ", err->error_description);
1155 else
1156 {
1157 /*
1158 * Try to get some more helpful detail into the error string. A 401
1159 * status in particular implies that the oauth_client_secret is
1160 * missing or wrong.
1161 */
1162 long response_code;
1163
1165
1166 if (response_code == 401)
1167 {
1168 actx_error(actx, actx->used_basic_auth
1169 ? gettext_noop("provider rejected the oauth_client_secret")
1170 : gettext_noop("provider requires client authentication, and no oauth_client_secret is set"));
1171 actx_error_str(actx, " ");
1172 }
1173 }
1174
1175 appendPQExpBuffer(&actx->errbuf, "(%s)", err->error);
1176}
#define gettext_noop(x)
Definition c.h:1285

References actx_error, actx_error_str, appendPQExpBuffer(), CHECK_GETINFO, err(), fb(), and gettext_noop.

Referenced by finish_device_authz(), and handle_token_response().

◆ register_socket()

static int register_socket ( CURL curl,
curl_socket_t  socket,
int  what,
void ctx,
void socketp 
)
static

Definition at line 1283 of file oauth-curl.c.

1285{
1286 struct async_ctx *actx = ctx;
1287
1288#if defined(HAVE_SYS_EPOLL_H)
1289 struct epoll_event ev = {0};
1290 int res;
1291 int op = EPOLL_CTL_ADD;
1292
1293 switch (what)
1294 {
1295 case CURL_POLL_IN:
1296 ev.events = EPOLLIN;
1297 break;
1298
1299 case CURL_POLL_OUT:
1300 ev.events = EPOLLOUT;
1301 break;
1302
1303 case CURL_POLL_INOUT:
1304 ev.events = EPOLLIN | EPOLLOUT;
1305 break;
1306
1307 case CURL_POLL_REMOVE:
1308 op = EPOLL_CTL_DEL;
1309 break;
1310
1311 default:
1312 actx_error_internal(actx, "unknown libcurl socket operation: %d", what);
1313 return -1;
1314 }
1315
1316 res = epoll_ctl(actx->mux, op, socket, &ev);
1317 if (res < 0 && errno == EEXIST)
1318 {
1319 /* We already had this socket in the poll set. */
1320 op = EPOLL_CTL_MOD;
1321 res = epoll_ctl(actx->mux, op, socket, &ev);
1322 }
1323
1324 if (res < 0)
1325 {
1326 switch (op)
1327 {
1328 case EPOLL_CTL_ADD:
1329 actx_error_internal(actx, "could not add to epoll set: %m");
1330 break;
1331
1332 case EPOLL_CTL_DEL:
1333 actx_error_internal(actx, "could not delete from epoll set: %m");
1334 break;
1335
1336 default:
1337 actx_error_internal(actx, "could not update epoll set: %m");
1338 }
1339
1340 return -1;
1341 }
1342
1343 return 0;
1344#elif defined(HAVE_SYS_EVENT_H)
1345 struct kevent ev[2];
1346 struct kevent ev_out[2];
1347 struct timespec timeout = {0};
1348 int nev = 0;
1349 int res;
1350
1351 /*
1352 * We don't know which of the events is currently registered, perhaps
1353 * both, so we always try to remove unneeded events. This means we need to
1354 * tolerate ENOENT below.
1355 */
1356 switch (what)
1357 {
1358 case CURL_POLL_IN:
1359 EV_SET(&ev[nev], socket, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, 0);
1360 nev++;
1362 nev++;
1363 break;
1364
1365 case CURL_POLL_OUT:
1366 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_ADD | EV_RECEIPT, 0, 0, 0);
1367 nev++;
1369 nev++;
1370 break;
1371
1372 case CURL_POLL_INOUT:
1373 EV_SET(&ev[nev], socket, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, 0);
1374 nev++;
1375 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_ADD | EV_RECEIPT, 0, 0, 0);
1376 nev++;
1377 break;
1378
1379 case CURL_POLL_REMOVE:
1381 nev++;
1383 nev++;
1384 break;
1385
1386 default:
1387 actx_error_internal(actx, "unknown libcurl socket operation: %d", what);
1388 return -1;
1389 }
1390
1391 Assert(nev <= lengthof(ev));
1393
1394 res = kevent(actx->mux, ev, nev, ev_out, nev, &timeout);
1395 if (res < 0)
1396 {
1397 actx_error_internal(actx, "could not modify kqueue: %m");
1398 return -1;
1399 }
1400
1401 /*
1402 * We can't use the simple errno version of kevent, because we need to
1403 * skip over ENOENT while still allowing a second change to be processed.
1404 * So we need a longer-form error checking loop.
1405 */
1406 for (int i = 0; i < res; ++i)
1407 {
1408 /*
1409 * EV_RECEIPT should guarantee one EV_ERROR result for every change,
1410 * whether successful or not. Failed entries contain a non-zero errno
1411 * in the data field.
1412 */
1413 Assert(ev_out[i].flags & EV_ERROR);
1414
1415 errno = ev_out[i].data;
1416 if (errno && errno != ENOENT)
1417 {
1418 switch (what)
1419 {
1420 case CURL_POLL_REMOVE:
1421 actx_error_internal(actx, "could not delete from kqueue: %m");
1422 break;
1423 default:
1424 actx_error_internal(actx, "could not add to kqueue: %m");
1425 }
1426 return -1;
1427 }
1428 }
1429
1430 return 0;
1431#else
1432#error register_socket is not implemented on this platform
1433#endif
1434}
#define lengthof(array)
Definition c.h:873
#define socket(af, type, protocol)
Definition win32_port.h:495

References actx_error_internal, Assert, fb(), i, lengthof, and socket.

Referenced by setup_curl_handles().

◆ register_timer()

static int register_timer ( CURLM curlm,
long  timeout,
void ctx 
)
static

Definition at line 1618 of file oauth-curl.c.

1619{
1620 struct async_ctx *actx = ctx;
1621
1622 /*
1623 * There might be an optimization opportunity here: if timeout == 0, we
1624 * could signal drive_request to immediately call
1625 * curl_multi_socket_action, rather than returning all the way up the
1626 * stack only to come right back. But it's not clear that the additional
1627 * code complexity is worth it.
1628 */
1629 if (!set_timer(actx, timeout))
1630 return -1; /* actx_error already called */
1631
1632 return 0;
1633}

References fb(), and set_timer().

Referenced by setup_curl_handles().

◆ report_type_mismatch()

static void report_type_mismatch ( struct oauth_parse ctx)
static

Definition at line 516 of file oauth-curl.c.

517{
518 char *msgfmt;
519
520 Assert(ctx->active);
521
522 /*
523 * At the moment, the only fields we're interested in are strings,
524 * numbers, and arrays of strings.
525 */
526 switch (ctx->active->type)
527 {
529 msgfmt = gettext_noop("field \"%s\" must be a string");
530 break;
531
533 msgfmt = gettext_noop("field \"%s\" must be a number");
534 break;
535
537 msgfmt = gettext_noop("field \"%s\" must be an array of strings");
538 break;
539
540 default:
541 Assert(false);
542 msgfmt = gettext_noop("field \"%s\" has unexpected type");
543 }
544
546}

References oauth_parse::active, Assert, fb(), gettext_noop, JSON_TOKEN_ARRAY_START, JSON_TOKEN_NUMBER, JSON_TOKEN_STRING, json_field::name, oauth_parse_set_error, and json_field::type.

Referenced by oauth_json_array_start(), oauth_json_object_start(), and oauth_json_scalar().

◆ set_timer()

static bool set_timer ( struct async_ctx actx,
long  timeout 
)
static

Definition at line 1497 of file oauth-curl.c.

1498{
1499#if defined(HAVE_SYS_EPOLL_H)
1500 struct itimerspec spec = {0};
1501
1502 if (timeout < 0)
1503 {
1504 /* the zero itimerspec will disarm the timer below */
1505 }
1506 else if (timeout == 0)
1507 {
1508 /*
1509 * A zero timeout means libcurl wants us to call back immediately.
1510 * That's not technically an option for timerfd, but we can make the
1511 * timeout ridiculously short.
1512 */
1513 spec.it_value.tv_nsec = 1;
1514 }
1515 else
1516 {
1517 spec.it_value.tv_sec = timeout / 1000;
1518 spec.it_value.tv_nsec = (timeout % 1000) * 1000000;
1519 }
1520
1521 if (timerfd_settime(actx->timerfd, 0 /* no flags */ , &spec, NULL) < 0)
1522 {
1523 actx_error_internal(actx, "setting timerfd to %ld: %m", timeout);
1524 return false;
1525 }
1526
1527 return true;
1528#elif defined(HAVE_SYS_EVENT_H)
1529 struct kevent ev;
1530
1531#ifdef __NetBSD__
1532
1533 /*
1534 * Work around NetBSD's rejection of zero timeouts (EINVAL), a bit like
1535 * timerfd above.
1536 */
1537 if (timeout == 0)
1538 timeout = 1;
1539#endif
1540
1541 /*
1542 * Always disable the timer, and remove it from the multiplexer, to clear
1543 * out any already-queued events. (On some BSDs, adding an EVFILT_TIMER to
1544 * a kqueue that already has one will clear stale events, but not on
1545 * macOS.)
1546 *
1547 * If there was no previous timer set, the kevent calls will result in
1548 * ENOENT, which is fine.
1549 */
1550 EV_SET(&ev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, 0);
1551 if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1552 {
1553 actx_error_internal(actx, "deleting kqueue timer: %m");
1554 return false;
1555 }
1556
1557 EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_DELETE, 0, 0, 0);
1558 if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1559 {
1560 actx_error_internal(actx, "removing kqueue timer from multiplexer: %m");
1561 return false;
1562 }
1563
1564 /* If we're not adding a timer, we're done. */
1565 if (timeout < 0)
1566 return true;
1567
1568 EV_SET(&ev, 1, EVFILT_TIMER, (EV_ADD | EV_ONESHOT), 0, timeout, 0);
1569 if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0)
1570 {
1571 actx_error_internal(actx, "setting kqueue timer to %ld: %m", timeout);
1572 return false;
1573 }
1574
1575 EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_ADD, 0, 0, 0);
1576 if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0)
1577 {
1578 actx_error_internal(actx, "adding kqueue timer to multiplexer: %m");
1579 return false;
1580 }
1581
1582 return true;
1583#else
1584#error set_timer is not implemented on this platform
1585#endif
1586}

References actx_error_internal, and fb().

Referenced by drain_timer_events(), pg_fe_run_oauth_flow_impl(), and register_timer().

◆ setup_curl_handles()

static bool setup_curl_handles ( struct async_ctx actx)
static

Definition at line 1755 of file oauth-curl.c.

1756{
1757 /*
1758 * Create our multi handle. This encapsulates the entire conversation with
1759 * libcurl for this connection.
1760 */
1761 actx->curlm = curl_multi_init();
1762 if (!actx->curlm)
1763 {
1764 /* We don't get a lot of feedback on the failure reason. */
1765 actx_error(actx, "failed to create libcurl multi handle");
1766 return false;
1767 }
1768
1769 /*
1770 * The multi handle tells us what to wait on using two callbacks. These
1771 * will manipulate actx->mux as needed.
1772 */
1774 CHECK_MSETOPT(actx, CURLMOPT_SOCKETDATA, actx, return false);
1776 CHECK_MSETOPT(actx, CURLMOPT_TIMERDATA, actx, return false);
1777
1778 /*
1779 * Set up an easy handle. All of our requests are made serially, so we
1780 * only ever need to keep track of one.
1781 */
1782 actx->curl = curl_easy_init();
1783 if (!actx->curl)
1784 {
1785 actx_error(actx, "failed to create libcurl handle");
1786 return false;
1787 }
1788
1789 /*
1790 * Multi-threaded applications must set CURLOPT_NOSIGNAL. This requires us
1791 * to handle the possibility of SIGPIPE ourselves using pq_block_sigpipe;
1792 * see pg_fe_run_oauth_flow().
1793 *
1794 * NB: If libcurl is not built against a friendly DNS resolver (c-ares or
1795 * threaded), setting this option prevents DNS lookups from timing out
1796 * correctly. We warn about this situation at configure time.
1797 *
1798 * TODO: Perhaps there's a clever way to warn the user about synchronous
1799 * DNS at runtime too? It's not immediately clear how to do that in a
1800 * helpful way: for many standard single-threaded use cases, the user
1801 * might not care at all, so spraying warnings to stderr would probably do
1802 * more harm than good.
1803 */
1804 CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L, return false);
1805
1806 if (actx->debug_flags & OAUTHDEBUG_UNSAFE_TRACE)
1807 {
1808 /*
1809 * Set a callback for retrieving error information from libcurl, the
1810 * function only takes effect when CURLOPT_VERBOSE has been set so
1811 * make sure the order is kept.
1812 */
1814 CHECK_SETOPT(actx, CURLOPT_VERBOSE, 1L, return false);
1815 }
1816
1817 CHECK_SETOPT(actx, CURLOPT_ERRORBUFFER, actx->curl_err, return false);
1818
1819 /*
1820 * Only HTTPS is allowed. (Debug mode additionally allows HTTP; this is
1821 * intended for testing only.)
1822 *
1823 * There's a bit of unfortunate complexity around the choice of
1824 * CURLoption. CURLOPT_PROTOCOLS is deprecated in modern Curls, but its
1825 * replacement didn't show up until relatively recently.
1826 */
1827 {
1828#if CURL_AT_LEAST_VERSION(7, 85, 0)
1829 const CURLoption popt = CURLOPT_PROTOCOLS_STR;
1830 const char *protos = "https";
1831 const char *const unsafe = "https,http";
1832#else
1833 const CURLoption popt = CURLOPT_PROTOCOLS;
1834 long protos = CURLPROTO_HTTPS;
1835 const long unsafe = CURLPROTO_HTTPS | CURLPROTO_HTTP;
1836#endif
1837
1838 if (actx->debug_flags & OAUTHDEBUG_UNSAFE_HTTP)
1839 protos = unsafe;
1840
1841 CHECK_SETOPT(actx, popt, protos, return false);
1842 }
1843
1844 /* Allow the user to change the trusted CA list. */
1845 if (actx->ca_file != NULL)
1846 CHECK_SETOPT(actx, CURLOPT_CAINFO, actx->ca_file, return false);
1847
1848 /*
1849 * Suppress the Accept header to make our request as minimal as possible.
1850 * (Ideally we would set it to "application/json" instead, but OpenID is
1851 * pretty strict when it comes to provider behavior, so we have to check
1852 * what comes back anyway.)
1853 */
1854 actx->headers = curl_slist_append(actx->headers, "Accept:");
1855 if (actx->headers == NULL)
1856 {
1857 actx_error(actx, "out of memory");
1858 return false;
1859 }
1860 CHECK_SETOPT(actx, CURLOPT_HTTPHEADER, actx->headers, return false);
1861
1862 return true;
1863}
static int register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx, void *socketp)
#define CHECK_MSETOPT(ACTX, OPT, VAL, FAILACTION)
Definition oauth-curl.c:433
static int debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *clientp)
static int register_timer(CURLM *curlm, long timeout, void *ctx)
#define OAUTHDEBUG_UNSAFE_TRACE
Definition oauth-debug.h:40

References actx_error, CHECK_MSETOPT, CHECK_SETOPT, debug_callback(), fb(), OAUTHDEBUG_UNSAFE_HTTP, OAUTHDEBUG_UNSAFE_TRACE, register_socket(), and register_timer().

Referenced by pg_start_oauthbearer().

◆ setup_multiplexer()

static bool setup_multiplexer ( struct async_ctx actx)
static

Definition at line 1225 of file oauth-curl.c.

1226{
1227#if defined(HAVE_SYS_EPOLL_H)
1228 struct epoll_event ev = {.events = EPOLLIN};
1229
1231 if (actx->mux < 0)
1232 {
1233 actx_error_internal(actx, "failed to create epoll set: %m");
1234 return false;
1235 }
1236
1238 if (actx->timerfd < 0)
1239 {
1240 actx_error_internal(actx, "failed to create timerfd: %m");
1241 return false;
1242 }
1243
1244 if (epoll_ctl(actx->mux, EPOLL_CTL_ADD, actx->timerfd, &ev) < 0)
1245 {
1246 actx_error_internal(actx, "failed to add timerfd to epoll set: %m");
1247 return false;
1248 }
1249
1250 return true;
1251#elif defined(HAVE_SYS_EVENT_H)
1252 actx->mux = kqueue();
1253 if (actx->mux < 0)
1254 {
1255 actx_error_internal(actx, "failed to create kqueue: %m");
1256 return false;
1257 }
1258
1259 /*
1260 * Originally, we set EVFILT_TIMER directly on the top-level multiplexer.
1261 * This makes it difficult to implement timer_expired(), though, so now we
1262 * set EVFILT_TIMER on a separate actx->timerfd, which is chained to
1263 * actx->mux while the timer is active.
1264 */
1265 actx->timerfd = kqueue();
1266 if (actx->timerfd < 0)
1267 {
1268 actx_error_internal(actx, "failed to create timer kqueue: %m");
1269 return false;
1270 }
1271
1272 return true;
1273#else
1274#error setup_multiplexer is not implemented on this platform
1275#endif
1276}

References actx_error_internal, and fb().

Referenced by pg_start_oauthbearer().

◆ start_device_authz()

static bool start_device_authz ( struct async_ctx actx,
PGconn conn 
)
static

Definition at line 2419 of file oauth-curl.c.

2420{
2421 const char *oauth_scope = actx->scope;
2422 const char *device_authz_uri = actx->provider.device_authorization_endpoint;
2423 PQExpBuffer work_buffer = &actx->work_data;
2424
2425 Assert(device_authz_uri); /* ensured by check_for_device_flow() */
2426
2427 /* Construct our request body. */
2429 if (oauth_scope && oauth_scope[0])
2430 build_urlencoded(work_buffer, "scope", oauth_scope);
2431
2433 return false;
2434
2436 {
2437 actx_error(actx, "out of memory");
2438 return false;
2439 }
2440
2441 /* Make our request. */
2443 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2444
2445 return start_request(actx);
2446}
static bool add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
static bool start_request(struct async_ctx *actx)

References actx_error, add_client_identification(), Assert, build_urlencoded(), CHECK_SETOPT, conn, fb(), PQExpBufferBroken, resetPQExpBuffer(), and start_request().

Referenced by pg_fe_run_oauth_flow_impl().

◆ start_discovery()

static bool start_discovery ( struct async_ctx actx,
const char discovery_uri 
)
static

Definition at line 2157 of file oauth-curl.c.

2158{
2159 CHECK_SETOPT(actx, CURLOPT_HTTPGET, 1L, return false);
2160 CHECK_SETOPT(actx, CURLOPT_URL, discovery_uri, return false);
2161
2162 return start_request(actx);
2163}

References CHECK_SETOPT, async_ctx::discovery_uri, fb(), and start_request().

Referenced by pg_fe_run_oauth_flow_impl().

◆ start_request()

static bool start_request ( struct async_ctx actx)
static

Definition at line 1916 of file oauth-curl.c.

1917{
1918 CURLMcode err;
1919
1920 resetPQExpBuffer(&actx->work_data);
1922 CHECK_SETOPT(actx, CURLOPT_WRITEDATA, actx, return false);
1923
1924 err = curl_multi_add_handle(actx->curlm, actx->curl);
1925 if (err)
1926 {
1927 actx_error(actx, "failed to queue HTTP request: %s",
1929 return false;
1930 }
1931
1932 /*
1933 * actx->running tracks the number of running handles, so we can
1934 * immediately call back if no waiting is needed.
1935 *
1936 * Even though this is nominally an asynchronous process, there are some
1937 * operations that can synchronously fail by this point (e.g. connections
1938 * to closed local ports) or even synchronously succeed if the stars align
1939 * (all the libcurl connection caches hit and the server is fast).
1940 */
1941 err = curl_multi_socket_action(actx->curlm, CURL_SOCKET_TIMEOUT, 0, &actx->running);
1942 if (err)
1943 {
1944 actx_error(actx, "asynchronous HTTP request failed: %s",
1946 return false;
1947 }
1948
1949 return true;
1950}
static size_t append_data(char *buf, size_t size, size_t nmemb, void *userdata)

References actx_error, append_data(), CHECK_SETOPT, err(), fb(), and resetPQExpBuffer().

Referenced by start_device_authz(), start_discovery(), and start_token_request().

◆ start_token_request()

static bool start_token_request ( struct async_ctx actx,
PGconn conn 
)
static

Definition at line 2506 of file oauth-curl.c.

2507{
2508 const char *token_uri = actx->provider.token_endpoint;
2509 const char *device_code = actx->authz.device_code;
2510 PQExpBuffer work_buffer = &actx->work_data;
2511
2512 Assert(token_uri); /* ensured by parse_provider() */
2513 Assert(device_code); /* ensured by parse_device_authz() */
2514
2515 /* Construct our request body. */
2517 build_urlencoded(work_buffer, "device_code", device_code);
2519
2521 return false;
2522
2524 {
2525 actx_error(actx, "out of memory");
2526 return false;
2527 }
2528
2529 /* Make our request. */
2530 CHECK_SETOPT(actx, CURLOPT_URL, token_uri, return false);
2531 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2532
2533 return start_request(actx);
2534}
#define OAUTH_GRANT_TYPE_DEVICE_CODE

References actx_error, add_client_identification(), Assert, build_urlencoded(), CHECK_SETOPT, conn, fb(), OAUTH_GRANT_TYPE_DEVICE_CODE, PQExpBufferBroken, resetPQExpBuffer(), and start_request().

Referenced by pg_fe_run_oauth_flow_impl().

◆ timer_expired()

static int timer_expired ( struct async_ctx actx)
static

Definition at line 1594 of file oauth-curl.c.

1595{
1596#if defined(HAVE_SYS_EPOLL_H) || defined(HAVE_SYS_EVENT_H)
1597 int res;
1598
1599 /* Is the timer ready? */
1600 res = PQsocketPoll(actx->timerfd, 1 /* forRead */ , 0, 0);
1601 if (res < 0)
1602 {
1603 actx_error(actx, "checking timer expiration: %m");
1604 return -1;
1605 }
1606
1607 return (res > 0);
1608#else
1609#error timer_expired is not implemented on this platform
1610#endif
1611}
int PQsocketPoll(int sock, int forRead, int forWrite, pg_usec_time_t end_time)
Definition fe-misc.c:1141

References actx_error, fb(), and PQsocketPoll().

Referenced by drain_timer_events().

◆ urlencode()

static char * urlencode ( const char s)
static

Definition at line 2113 of file oauth-curl.c.

2114{
2116
2119
2120 return PQExpBufferDataBroken(buf) ? NULL : buf.data;
2121}

References append_urlencoded(), buf, fb(), initPQExpBuffer(), and PQExpBufferDataBroken.

Referenced by add_client_identification().