PostgreSQL Source Code git master
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
fe-auth-oauth-curl.c File Reference
#include "postgres_fe.h"
#include <curl/curl.h>
#include <math.h>
#include <unistd.h>
#include "common/jsonapi.h"
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "libpq-int.h"
#include "mb/pg_wchar.h"
Include dependency graph for fe-auth-oauth-curl.c:

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 actx_error(ACTX, FMT, ...)    appendPQExpBuffer(&(ACTX)->errbuf, libpq_gettext(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 CURL_IGNORE_DEPRECATION(x)   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 (PGconn *conn, struct async_ctx *actx)
 
void pg_fe_cleanup_oauth_flow (PGconn *conn)
 
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 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 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 char * urlencode (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 (PGconn *conn)
 
static PostgresPollingStatusType pg_fe_run_oauth_flow_impl (PGconn *conn)
 
PostgresPollingStatusType pg_fe_run_oauth_flow (PGconn *conn)
 

Macro Definition Documentation

◆ actx_error

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

Definition at line 323 of file fe-auth-oauth-curl.c.

◆ actx_error_str

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

Definition at line 326 of file fe-auth-oauth-curl.c.

◆ CHECK_GETINFO

#define CHECK_GETINFO (   ACTX,
  INFO,
  OUT,
  FAILACTION 
)
Value:
do { \
struct async_ctx *_actx = (ACTX); \
CURLcode _getinfoerr = curl_easy_getinfo(_actx->curl, INFO, OUT); \
if (_getinfoerr) { \
actx_error(_actx, "failed to get %s from OAuth response: %s",\
#INFO, curl_easy_strerror(_getinfoerr)); \
FAILACTION; \
} \
} while (0)
#define INFO
Definition: elog.h:34

Definition at line 356 of file fe-auth-oauth-curl.c.

◆ CHECK_MSETOPT

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

Definition at line 334 of file fe-auth-oauth-curl.c.

◆ CHECK_SETOPT

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

Definition at line 345 of file fe-auth-oauth-curl.c.

◆ CURL_IGNORE_DEPRECATION

#define CURL_IGNORE_DEPRECATION (   x)    x

Definition at line 1779 of file fe-auth-oauth-curl.c.

◆ HTTPS_SCHEME

#define HTTPS_SCHEME   "https://"

Definition at line 2083 of file fe-auth-oauth-curl.c.

◆ MAX_OAUTH_RESPONSE_SIZE

#define MAX_OAUTH_RESPONSE_SIZE   (256 * 1024)

Definition at line 47 of file fe-auth-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 2084 of file fe-auth-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 410 of file fe-auth-oauth-curl.c.

◆ PG_OAUTH_OPTIONAL

#define PG_OAUTH_OPTIONAL   false

Definition at line 398 of file fe-auth-oauth-curl.c.

◆ PG_OAUTH_REQUIRED

#define PG_OAUTH_REQUIRED   true

Definition at line 397 of file fe-auth-oauth-curl.c.

Enumeration Type Documentation

◆ OAuthStep

enum OAuthStep
Enumerator
OAUTH_STEP_INIT 
OAUTH_STEP_DISCOVERY 
OAUTH_STEP_DEVICE_AUTHORIZATION 
OAUTH_STEP_TOKEN_REQUEST 
OAUTH_STEP_WAIT_INTERVAL 

Definition at line 164 of file fe-auth-oauth-curl.c.

165{
166 OAUTH_STEP_INIT = 0,
171};
@ OAUTH_STEP_DEVICE_AUTHORIZATION
@ OAUTH_STEP_WAIT_INTERVAL
@ OAUTH_STEP_INIT
@ OAUTH_STEP_DISCOVERY
@ OAUTH_STEP_TOKEN_REQUEST

Function Documentation

◆ add_client_identification()

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

Definition at line 2151 of file fe-auth-oauth-curl.c.

2152{
2153 bool success = false;
2154 char *username = NULL;
2155 char *password = NULL;
2156
2157 if (conn->oauth_client_secret) /* Zero-length secrets are permitted! */
2158 {
2159 /*----
2160 * Use HTTP Basic auth to send the client_id and secret. Per RFC 6749,
2161 * Sec. 2.3.1,
2162 *
2163 * Including the client credentials in the request-body using the
2164 * two parameters is NOT RECOMMENDED and SHOULD be limited to
2165 * clients unable to directly utilize the HTTP Basic authentication
2166 * scheme (or other password-based HTTP authentication schemes).
2167 *
2168 * Additionally:
2169 *
2170 * The client identifier is encoded using the
2171 * "application/x-www-form-urlencoded" encoding algorithm per Appendix
2172 * B, and the encoded value is used as the username; the client
2173 * password is encoded using the same algorithm and used as the
2174 * password.
2175 *
2176 * (Appendix B modifies application/x-www-form-urlencoded by requiring
2177 * an initial UTF-8 encoding step. Since the client ID and secret must
2178 * both be 7-bit ASCII -- RFC 6749 Appendix A -- we don't worry about
2179 * that in this function.)
2180 *
2181 * client_id is not added to the request body in this case. Not only
2182 * would it be redundant, but some providers in the wild (e.g. Okta)
2183 * refuse to accept it.
2184 */
2187
2188 if (!username || !password)
2189 {
2190 actx_error(actx, "out of memory");
2191 goto cleanup;
2192 }
2193
2194 CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_BASIC, goto cleanup);
2195 CHECK_SETOPT(actx, CURLOPT_USERNAME, username, goto cleanup);
2196 CHECK_SETOPT(actx, CURLOPT_PASSWORD, password, goto cleanup);
2197
2198 actx->used_basic_auth = true;
2199 }
2200 else
2201 {
2202 /*
2203 * If we're not otherwise authenticating, client_id is REQUIRED in the
2204 * request body.
2205 */
2206 build_urlencoded(reqbody, "client_id", conn->oauth_client_id);
2207
2208 CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, goto cleanup);
2209 actx->used_basic_auth = false;
2210 }
2211
2212 success = true;
2213
2214cleanup:
2215 free(username);
2216 free(password);
2217
2218 return success;
2219}
static void cleanup(void)
Definition: bootstrap.c:713
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)
#define actx_error(ACTX, FMT,...)
#define free(a)
Definition: header.h:65
static bool success
Definition: initdb.c:186
static char * username
Definition: initdb.c:153
static char * password
Definition: streamutil.c:51
PGconn * conn
Definition: streamutil.c:52
char * oauth_client_id
Definition: libpq-int.h:445
char * oauth_client_secret
Definition: libpq-int.h:446

References actx_error, build_urlencoded(), CHECK_SETOPT, cleanup(), conn, free, pg_conn::oauth_client_id, pg_conn::oauth_client_secret, password, success, urlencode(), async_ctx::used_basic_auth, and username.

Referenced by start_device_authz(), and start_token_request().

◆ append_data()

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

Definition at line 1698 of file fe-auth-oauth-curl.c.

1699{
1700 struct async_ctx *actx = userdata;
1701 PQExpBuffer resp = &actx->work_data;
1702 size_t len = size * nmemb;
1703
1704 /* In case we receive data over the threshold, abort the transfer */
1705 if ((resp->len + len) > MAX_OAUTH_RESPONSE_SIZE)
1706 {
1707 actx_error(actx, "response is too large");
1708 return 0;
1709 }
1710
1711 /* The data passed from libcurl is not null-terminated */
1713
1714 /*
1715 * Signal an error in order to abort the transfer in case we ran out of
1716 * memory in accepting the data.
1717 */
1718 if (PQExpBufferBroken(resp))
1719 {
1720 actx_error(actx, "out of memory");
1721 return 0;
1722 }
1723
1724 return len;
1725}
#define MAX_OAUTH_RESPONSE_SIZE
const void size_t len
static char * buf
Definition: pg_test_fsync.c:72
void appendBinaryPQExpBuffer(PQExpBuffer str, const char *data, size_t datalen)
Definition: pqexpbuffer.c:397
#define PQExpBufferBroken(str)
Definition: pqexpbuffer.h:59
PQExpBufferData work_data

References actx_error, appendBinaryPQExpBuffer(), buf, PQExpBufferData::len, len, MAX_OAUTH_RESPONSE_SIZE, PQExpBufferBroken, and async_ctx::work_data.

Referenced by start_request().

◆ append_urlencoded()

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

Definition at line 1887 of file fe-auth-oauth-curl.c.

1888{
1889 char *escaped;
1890 char *haystack;
1891 char *match;
1892
1893 /* The first parameter to curl_easy_escape is deprecated by Curl */
1894 escaped = curl_easy_escape(NULL, s, 0);
1895 if (!escaped)
1896 {
1897 termPQExpBuffer(buf); /* mark the buffer broken */
1898 return;
1899 }
1900
1901 /*
1902 * curl_easy_escape() almost does what we want, but we need the
1903 * query-specific flavor which uses '+' instead of '%20' for spaces. The
1904 * Curl command-line tool does this with a simple search-and-replace, so
1905 * follow its lead.
1906 */
1907 haystack = escaped;
1908
1909 while ((match = strstr(haystack, "%20")) != NULL)
1910 {
1911 /* Append the unmatched portion, followed by the plus sign. */
1912 appendBinaryPQExpBuffer(buf, haystack, match - haystack);
1914
1915 /* Keep searching after the match. */
1916 haystack = match + 3 /* strlen("%20") */ ;
1917 }
1918
1919 /* Push the remainder of the string onto the buffer. */
1920 appendPQExpBufferStr(buf, haystack);
1921
1922 curl_free(escaped);
1923}
void appendPQExpBufferChar(PQExpBuffer str, char ch)
Definition: pqexpbuffer.c:378
void appendPQExpBufferStr(PQExpBuffer str, const char *data)
Definition: pqexpbuffer.c:367
void termPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:129

References appendBinaryPQExpBuffer(), appendPQExpBufferChar(), appendPQExpBufferStr(), buf, 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 1945 of file fe-auth-oauth-curl.c.

1946{
1947 if (buf->len)
1949
1953}
static void append_urlencoded(PQExpBuffer buf, const char *s)
static struct @162 value

References append_urlencoded(), appendPQExpBufferChar(), buf, sort-test::key, 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 698 of file fe-auth-oauth-curl.c.

699{
700 const size_t type_len = strlen(type);
701 char *content_type;
702
703 CHECK_GETINFO(actx, CURLINFO_CONTENT_TYPE, &content_type, return false);
704
705 if (!content_type)
706 {
707 actx_error(actx, "no content type was provided");
708 return false;
709 }
710
711 /*
712 * We need to perform a length limited comparison and not compare the
713 * whole string.
714 */
715 if (pg_strncasecmp(content_type, type, type_len) != 0)
716 goto fail;
717
718 /* On an exact match, we're done. */
719 Assert(strlen(content_type) >= type_len);
720 if (content_type[type_len] == '\0')
721 return true;
722
723 /*
724 * Only a semicolon (optionally preceded by HTTP optional whitespace) is
725 * acceptable after the prefix we checked. This marks the start of media
726 * type parameters, which we currently have no use for.
727 */
728 for (size_t i = type_len; content_type[i]; ++i)
729 {
730 switch (content_type[i])
731 {
732 case ';':
733 return true; /* success! */
734
735 case ' ':
736 case '\t':
737 /* HTTP optional whitespace allows only spaces and htabs. */
738 break;
739
740 default:
741 goto fail;
742 }
743 }
744
745fail:
746 actx_error(actx, "unexpected content type: \"%s\"", content_type);
747 return false;
748}
#define CHECK_GETINFO(ACTX, INFO, OUT, FAILACTION)
Assert(PointerIsAligned(start, uint64))
int i
Definition: isn.c:72
int pg_strncasecmp(const char *s1, const char *s2, size_t n)
Definition: pgstrcasecmp.c:69
const char * type

References actx_error, Assert(), CHECK_GETINFO, 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 2092 of file fe-auth-oauth-curl.c.

2093{
2094 const struct provider *provider = &actx->provider;
2095
2096 Assert(provider->issuer); /* ensured by parse_provider() */
2097 Assert(provider->token_endpoint); /* ensured by parse_provider() */
2098
2100 {
2101 actx_error(actx,
2102 "issuer \"%s\" does not provide a device authorization endpoint",
2103 provider->issuer);
2104 return false;
2105 }
2106
2107 /*
2108 * The original implementation checked that OAUTH_GRANT_TYPE_DEVICE_CODE
2109 * was present in the discovery document's grant_types_supported list. MS
2110 * Entra does not advertise this grant type, though, and since it doesn't
2111 * make sense to stand up a device_authorization_endpoint without also
2112 * accepting device codes at the token_endpoint, that's the only thing we
2113 * currently require.
2114 */
2115
2116 /*
2117 * Although libcurl will fail later if the URL contains an unsupported
2118 * scheme, that error message is going to be a bit opaque. This is a
2119 * decent time to bail out if we're not using HTTPS for the endpoints
2120 * we'll use for the flow.
2121 */
2122 if (!actx->debugging)
2123 {
2125 HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
2126 {
2127 actx_error(actx,
2128 "device authorization endpoint \"%s\" must use HTTPS",
2130 return false;
2131 }
2132
2134 HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
2135 {
2136 actx_error(actx,
2137 "token endpoint \"%s\" must use HTTPS",
2139 return false;
2140 }
2141 }
2142
2143 return true;
2144}
#define HTTPS_SCHEME
struct provider provider
char * device_authorization_endpoint
char * token_endpoint

References actx_error, Assert(), async_ctx::debugging, provider::device_authorization_endpoint, HTTPS_SCHEME, provider::issuer, pg_strncasecmp(), async_ctx::provider, 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 2048 of file fe-auth-oauth-curl.c.

2049{
2050 const struct provider *provider = &actx->provider;
2051
2052 Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
2053 Assert(provider->issuer); /* ensured by parse_provider() */
2054
2055 /*---
2056 * We require strict equality for issuer identifiers -- no path or case
2057 * normalization, no substitution of default ports and schemes, etc. This
2058 * is done to match the rules in OIDC Discovery Sec. 4.3 for config
2059 * validation:
2060 *
2061 * The issuer value returned MUST be identical to the Issuer URL that
2062 * was used as the prefix to /.well-known/openid-configuration to
2063 * retrieve the configuration information.
2064 *
2065 * as well as the rules set out in RFC 9207 for avoiding mix-up attacks:
2066 *
2067 * Clients MUST then [...] compare the result to the issuer identifier
2068 * of the authorization server where the authorization request was
2069 * sent to. This comparison MUST use simple string comparison as defined
2070 * in Section 6.2.1 of [RFC3986].
2071 */
2072 if (strcmp(conn->oauth_issuer_id, provider->issuer) != 0)
2073 {
2074 actx_error(actx,
2075 "the issuer identifier (%s) does not match oauth_issuer (%s)",
2077 return false;
2078 }
2079
2080 return true;
2081}
char * oauth_issuer_id
Definition: libpq-int.h:442

References actx_error, Assert(), conn, provider::issuer, pg_conn::oauth_issuer_id, and async_ctx::provider.

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 1482 of file fe-auth-oauth-curl.c.

1484{
1485 const char *prefix;
1486 bool printed_prefix = false;
1488
1489 /* Prefixes are modeled off of the default libcurl debug output. */
1490 switch (type)
1491 {
1492 case CURLINFO_TEXT:
1493 prefix = "*";
1494 break;
1495
1496 case CURLINFO_HEADER_IN: /* fall through */
1497 case CURLINFO_DATA_IN:
1498 prefix = "<";
1499 break;
1500
1501 case CURLINFO_HEADER_OUT: /* fall through */
1502 case CURLINFO_DATA_OUT:
1503 prefix = ">";
1504 break;
1505
1506 default:
1507 return 0;
1508 }
1509
1511
1512 /*
1513 * Split the output into lines for readability; sometimes multiple headers
1514 * are included in a single call. We also don't allow unprintable ASCII
1515 * through without a basic <XX> escape.
1516 */
1517 for (int i = 0; i < size; i++)
1518 {
1519 char c = data[i];
1520
1521 if (!printed_prefix)
1522 {
1523 appendPQExpBuffer(&buf, "[libcurl] %s ", prefix);
1524 printed_prefix = true;
1525 }
1526
1527 if (c >= 0x20 && c <= 0x7E)
1529 else if ((type == CURLINFO_HEADER_IN
1530 || type == CURLINFO_HEADER_OUT
1531 || type == CURLINFO_TEXT)
1532 && (c == '\r' || c == '\n'))
1533 {
1534 /*
1535 * Don't bother emitting <0D><0A> for headers and text; it's not
1536 * helpful noise.
1537 */
1538 }
1539 else
1540 appendPQExpBuffer(&buf, "<%02X>", c);
1541
1542 if (c == '\n')
1543 {
1545 printed_prefix = false;
1546 }
1547 }
1548
1549 if (printed_prefix)
1550 appendPQExpBufferChar(&buf, '\n'); /* finish the line */
1551
1552 fprintf(stderr, "%s", buf.data);
1554 return 0;
1555}
#define fprintf(file, fmt, msg)
Definition: cubescan.l:21
const void * data
void initPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:90
void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
Definition: pqexpbuffer.c:265
char * c

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

Referenced by setup_curl_handles().

◆ drive_request()

static PostgresPollingStatusType drive_request ( struct async_ctx actx)
static

Definition at line 1787 of file fe-auth-oauth-curl.c.

1788{
1789 CURLMcode err;
1790 CURLMsg *msg;
1791 int msgs_left;
1792 bool done;
1793
1794 if (actx->running)
1795 {
1796 /*---
1797 * There's an async request in progress. Pump the multi handle.
1798 *
1799 * curl_multi_socket_all() is officially deprecated, because it's
1800 * inefficient and pointless if your event loop has already handed you
1801 * the exact sockets that are ready. But that's not our use case --
1802 * our client has no way to tell us which sockets are ready. (They
1803 * don't even know there are sockets to begin with.)
1804 *
1805 * We can grab the list of triggered events from the multiplexer
1806 * ourselves, but that's effectively what curl_multi_socket_all() is
1807 * going to do. And there are currently no plans for the Curl project
1808 * to remove or break this API, so ignore the deprecation. See
1809 *
1810 * https://curl.se/mail/lib-2024-11/0028.html
1811 *
1812 */
1814 err = curl_multi_socket_all(actx->curlm, &actx->running);
1815 )
1816
1817 if (err)
1818 {
1819 actx_error(actx, "asynchronous HTTP request failed: %s",
1820 curl_multi_strerror(err));
1821 return PGRES_POLLING_FAILED;
1822 }
1823
1824 if (actx->running)
1825 {
1826 /* We'll come back again. */
1827 return PGRES_POLLING_READING;
1828 }
1829 }
1830
1831 done = false;
1832 while ((msg = curl_multi_info_read(actx->curlm, &msgs_left)) != NULL)
1833 {
1834 if (msg->msg != CURLMSG_DONE)
1835 {
1836 /*
1837 * Future libcurl versions may define new message types; we don't
1838 * know how to handle them, so we'll ignore them.
1839 */
1840 continue;
1841 }
1842
1843 /* First check the status of the request itself. */
1844 if (msg->data.result != CURLE_OK)
1845 {
1846 /*
1847 * If a more specific error hasn't already been reported, use
1848 * libcurl's description.
1849 */
1850 if (actx->errbuf.len == 0)
1851 actx_error_str(actx, curl_easy_strerror(msg->data.result));
1852
1853 return PGRES_POLLING_FAILED;
1854 }
1855
1856 /* Now remove the finished handle; we'll add it back later if needed. */
1857 err = curl_multi_remove_handle(actx->curlm, msg->easy_handle);
1858 if (err)
1859 {
1860 actx_error(actx, "libcurl easy handle removal failed: %s",
1861 curl_multi_strerror(err));
1862 return PGRES_POLLING_FAILED;
1863 }
1864
1865 done = true;
1866 }
1867
1868 /* Sanity check. */
1869 if (!done)
1870 {
1871 actx_error(actx, "no result was retrieved for the finished handle");
1872 return PGRES_POLLING_FAILED;
1873 }
1874
1875 return PGRES_POLLING_OK;
1876}
void err(int eval, const char *fmt,...)
Definition: err.c:43
#define actx_error_str(ACTX, S)
#define CURL_IGNORE_DEPRECATION(x)
if(TABLE==NULL||TABLE_index==NULL)
Definition: isn.c:76
@ PGRES_POLLING_OK
Definition: libpq-fe.h:117
@ PGRES_POLLING_READING
Definition: libpq-fe.h:115
@ PGRES_POLLING_FAILED
Definition: libpq-fe.h:114
PQExpBufferData errbuf

References actx_error, actx_error_str, CURL_IGNORE_DEPRECATION, async_ctx::curlm, err(), async_ctx::errbuf, PQExpBufferData::len, PGRES_POLLING_FAILED, PGRES_POLLING_OK, PGRES_POLLING_READING, and async_ctx::running.

Referenced by pg_fe_run_oauth_flow_impl().

◆ finish_device_authz()

static bool finish_device_authz ( struct async_ctx actx)
static

Definition at line 2262 of file fe-auth-oauth-curl.c.

2263{
2264 long response_code;
2265
2266 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2267
2268 /*
2269 * Per RFC 8628, Section 3, a successful device authorization response
2270 * uses 200 OK.
2271 */
2272 if (response_code == 200)
2273 {
2274 actx->errctx = "failed to parse device authorization";
2275 if (!parse_device_authz(actx, &actx->authz))
2276 return false; /* error message already set */
2277
2278 return true;
2279 }
2280
2281 /*
2282 * The device authorization endpoint uses the same error response as the
2283 * token endpoint, so the error handling roughly follows
2284 * finish_token_request(). The key difference is that an error here is
2285 * immediately fatal.
2286 */
2287 if (response_code == 400 || response_code == 401)
2288 {
2289 struct token_error err = {0};
2290
2291 if (!parse_token_error(actx, &err))
2292 {
2294 return false;
2295 }
2296
2297 /* Copy the token error into the context error buffer */
2298 record_token_error(actx, &err);
2299
2301 return false;
2302 }
2303
2304 /* Any other response codes are considered invalid */
2305 actx_error(actx, "unexpected response code %ld", response_code);
2306 return false;
2307}
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)
struct device_authz authz
const char * errctx

References actx_error, async_ctx::authz, CHECK_GETINFO, err(), async_ctx::errctx, free_token_error(), 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 1983 of file fe-auth-oauth-curl.c.

1984{
1985 long response_code;
1986
1987 /*----
1988 * Now check the response. OIDC Discovery 1.0 is pretty strict:
1989 *
1990 * A successful response MUST use the 200 OK HTTP status code and
1991 * return a JSON object using the application/json content type that
1992 * contains a set of Claims as its members that are a subset of the
1993 * Metadata values defined in Section 3.
1994 *
1995 * Compared to standard HTTP semantics, this makes life easy -- we don't
1996 * need to worry about redirections (which would call the Issuer host
1997 * validation into question), or non-authoritative responses, or any other
1998 * complications.
1999 */
2000 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2001
2002 if (response_code != 200)
2003 {
2004 actx_error(actx, "unexpected response code %ld", response_code);
2005 return false;
2006 }
2007
2008 /*
2009 * Pull the fields we care about from the document.
2010 */
2011 actx->errctx = "failed to parse OpenID discovery document";
2012 if (!parse_provider(actx, &actx->provider))
2013 return false; /* error message already set */
2014
2015 /*
2016 * Fill in any defaults for OPTIONAL/RECOMMENDED fields we care about.
2017 */
2019 {
2020 /*
2021 * Per Section 3, the default is ["authorization_code", "implicit"].
2022 */
2023 struct curl_slist *temp = actx->provider.grant_types_supported;
2024
2025 temp = curl_slist_append(temp, "authorization_code");
2026 if (temp)
2027 {
2028 temp = curl_slist_append(temp, "implicit");
2029 }
2030
2031 if (!temp)
2032 {
2033 actx_error(actx, "out of memory");
2034 return false;
2035 }
2036
2037 actx->provider.grant_types_supported = temp;
2038 }
2039
2040 return true;
2041}
static bool parse_provider(struct async_ctx *actx, struct provider *provider)
struct curl_slist * grant_types_supported

References actx_error, CHECK_GETINFO, async_ctx::errctx, provider::grant_types_supported, parse_provider(), and async_ctx::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 2351 of file fe-auth-oauth-curl.c.

2352{
2353 long response_code;
2354
2355 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2356
2357 /*
2358 * Per RFC 6749, Section 5, a successful response uses 200 OK.
2359 */
2360 if (response_code == 200)
2361 {
2362 actx->errctx = "failed to parse access token response";
2363 if (!parse_access_token(actx, tok))
2364 return false; /* error message already set */
2365
2366 return true;
2367 }
2368
2369 /*
2370 * An error response uses either 400 Bad Request or 401 Unauthorized.
2371 * There are references online to implementations using 403 for error
2372 * return which would violate the specification. For now we stick to the
2373 * specification but we might have to revisit this.
2374 */
2375 if (response_code == 400 || response_code == 401)
2376 {
2377 if (!parse_token_error(actx, &tok->err))
2378 return false;
2379
2380 return true;
2381 }
2382
2383 /* Any other response codes are considered invalid */
2384 actx_error(actx, "unexpected response code %ld", response_code);
2385 return false;
2386}
static bool parse_access_token(struct async_ctx *actx, struct token *tok)
struct token_error err

References actx_error, CHECK_GETINFO, token::err, async_ctx::errctx, parse_access_token(), and parse_token_error().

Referenced by handle_token_response().

◆ free_async_ctx()

static void free_async_ctx ( PGconn conn,
struct async_ctx actx 
)
static

Definition at line 237 of file fe-auth-oauth-curl.c.

238{
239 /*
240 * In general, none of the error cases below should ever happen if we have
241 * no bugs above. But if we do hit them, surfacing those errors somehow
242 * might be the only way to have a chance to debug them.
243 *
244 * TODO: At some point it'd be nice to have a standard way to warn about
245 * teardown failures. Appending to the connection's error message only
246 * helps if the bug caused a connection failure; otherwise it'll be
247 * buried...
248 */
249
250 if (actx->curlm && actx->curl)
251 {
252 CURLMcode err = curl_multi_remove_handle(actx->curlm, actx->curl);
253
254 if (err)
256 "libcurl easy handle removal failed: %s",
257 curl_multi_strerror(err));
258 }
259
260 if (actx->curl)
261 {
262 /*
263 * curl_multi_cleanup() doesn't free any associated easy handles; we
264 * need to do that separately. We only ever have one easy handle per
265 * multi handle.
266 */
267 curl_easy_cleanup(actx->curl);
268 }
269
270 if (actx->curlm)
271 {
272 CURLMcode err = curl_multi_cleanup(actx->curlm);
273
274 if (err)
276 "libcurl multi handle cleanup failed: %s",
277 curl_multi_strerror(err));
278 }
279
280 free_provider(&actx->provider);
281 free_device_authz(&actx->authz);
282
283 curl_slist_free_all(actx->headers);
285 termPQExpBuffer(&actx->errbuf);
286
287 if (actx->mux != PGINVALID_SOCKET)
288 close(actx->mux);
289 if (actx->timerfd >= 0)
290 close(actx->timerfd);
291
292 free(actx);
293}
static void free_provider(struct provider *provider)
static void free_device_authz(struct device_authz *authz)
void libpq_append_conn_error(PGconn *conn, const char *fmt,...)
Definition: fe-misc.c:1381
#define close(a)
Definition: win32.h:12
#define PGINVALID_SOCKET
Definition: port.h:31
struct curl_slist * headers

References async_ctx::authz, close, conn, async_ctx::curl, async_ctx::curlm, err(), async_ctx::errbuf, free, free_device_authz(), free_provider(), async_ctx::headers, libpq_append_conn_error(), async_ctx::mux, PGINVALID_SOCKET, async_ctx::provider, termPQExpBuffer(), async_ctx::timerfd, and async_ctx::work_data.

Referenced by pg_fe_cleanup_oauth_flow().

◆ free_device_authz()

static void free_device_authz ( struct device_authz authz)
static

◆ free_provider()

static void free_provider ( struct provider provider)
static

◆ free_token()

static void free_token ( struct token tok)
static

Definition at line 152 of file fe-auth-oauth-curl.c.

153{
154 free(tok->access_token);
155 free(tok->token_type);
156 free_token_error(&tok->err);
157}
char * token_type
char * access_token

References token::access_token, token::err, free, free_token_error(), and token::token_type.

Referenced by handle_token_response().

◆ free_token_error()

static void free_token_error ( struct token_error err)
static

Definition at line 126 of file fe-auth-oauth-curl.c.

127{
128 free(err->error);
129 free(err->error_description);
130}

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 2398 of file fe-auth-oauth-curl.c.

2399{
2400 bool success = false;
2401 struct token tok = {0};
2402 const struct token_error *err;
2403
2404 if (!finish_token_request(actx, &tok))
2405 goto token_cleanup;
2406
2407 /* A successful token request gives either a token or an in-band error. */
2408 Assert(tok.access_token || tok.err.error);
2409
2410 if (tok.access_token)
2411 {
2412 *token = tok.access_token;
2413 tok.access_token = NULL;
2414
2415 success = true;
2416 goto token_cleanup;
2417 }
2418
2419 /*
2420 * authorization_pending and slow_down are the only acceptable errors;
2421 * anything else and we bail. These are defined in RFC 8628, Sec. 3.5.
2422 */
2423 err = &tok.err;
2424 if (strcmp(err->error, "authorization_pending") != 0 &&
2425 strcmp(err->error, "slow_down") != 0)
2426 {
2427 record_token_error(actx, err);
2428 goto token_cleanup;
2429 }
2430
2431 /*
2432 * A slow_down error requires us to permanently increase our retry
2433 * interval by five seconds.
2434 */
2435 if (strcmp(err->error, "slow_down") == 0)
2436 {
2437 int prev_interval = actx->authz.interval;
2438
2439 actx->authz.interval += 5;
2440 if (actx->authz.interval < prev_interval)
2441 {
2442 actx_error(actx, "slow_down interval overflow");
2443 goto token_cleanup;
2444 }
2445 }
2446
2447 success = true;
2448
2449token_cleanup:
2450 free_token(&tok);
2451 return success;
2452}
static bool finish_token_request(struct async_ctx *actx, struct token *tok)
static void free_token(struct token *tok)

References token::access_token, actx_error, Assert(), async_ctx::authz, token::err, err(), token_error::error, finish_token_request(), free_token(), device_authz::interval, record_token_error(), and success.

Referenced by pg_fe_run_oauth_flow_impl().

◆ initialize_curl()

static bool initialize_curl ( PGconn conn)
static

Definition at line 2503 of file fe-auth-oauth-curl.c.

2504{
2505 /*
2506 * Don't let the compiler play tricks with this variable. In the
2507 * HAVE_THREADSAFE_CURL_GLOBAL_INIT case, we don't care if two threads
2508 * enter simultaneously, but we do care if this gets set transiently to
2509 * PG_BOOL_YES/NO in cases where that's not the final answer.
2510 */
2511 static volatile PGTernaryBool init_successful = PG_BOOL_UNKNOWN;
2512#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
2513 curl_version_info_data *info;
2514#endif
2515
2516#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2517
2518 /*
2519 * Lock around the whole function. If a libpq client performs its own work
2520 * with libcurl, it must either ensure that Curl is initialized safely
2521 * before calling us (in which case our call will be a no-op), or else it
2522 * must guard its own calls to curl_global_init() with a registered
2523 * threadlock handler. See PQregisterThreadLock().
2524 */
2525 pglock_thread();
2526#endif
2527
2528 /*
2529 * Skip initialization if we've already done it. (Curl tracks the number
2530 * of calls; there's no point in incrementing the counter every time we
2531 * connect.)
2532 */
2533 if (init_successful == PG_BOOL_YES)
2534 goto done;
2535 else if (init_successful == PG_BOOL_NO)
2536 {
2538 "curl_global_init previously failed during OAuth setup");
2539 goto done;
2540 }
2541
2542 /*
2543 * We know we've already initialized Winsock by this point (see
2544 * pqMakeEmptyPGconn()), so we should be able to safely skip that bit. But
2545 * we have to tell libcurl to initialize everything else, because other
2546 * pieces of our client executable may already be using libcurl for their
2547 * own purposes. If we initialize libcurl with only a subset of its
2548 * features, we could break those other clients nondeterministically, and
2549 * that would probably be a nightmare to debug.
2550 *
2551 * If some other part of the program has already called this, it's a
2552 * no-op.
2553 */
2554 if (curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32) != CURLE_OK)
2555 {
2557 "curl_global_init failed during OAuth setup");
2558 init_successful = PG_BOOL_NO;
2559 goto done;
2560 }
2561
2562#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
2563
2564 /*
2565 * If we determined at configure time that the Curl installation is
2566 * thread-safe, our job here is much easier. We simply initialize above
2567 * without any locking (concurrent or duplicated calls are fine in that
2568 * situation), then double-check to make sure the runtime setting agrees,
2569 * to try to catch silent downgrades.
2570 */
2571 info = curl_version_info(CURLVERSION_NOW);
2572 if (!(info->features & CURL_VERSION_THREADSAFE))
2573 {
2574 /*
2575 * In a downgrade situation, the damage is already done. Curl global
2576 * state may be corrupted. Be noisy.
2577 */
2578 libpq_append_conn_error(conn, "libcurl is no longer thread-safe\n"
2579 "\tCurl initialization was reported thread-safe when libpq\n"
2580 "\twas compiled, but the currently installed version of\n"
2581 "\tlibcurl reports that it is not. Recompile libpq against\n"
2582 "\tthe installed version of libcurl.");
2583 init_successful = PG_BOOL_NO;
2584 goto done;
2585 }
2586#endif
2587
2588 init_successful = PG_BOOL_YES;
2589
2590done:
2591#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2593#endif
2594 return (init_successful == PG_BOOL_YES);
2595}
PGTernaryBool
Definition: libpq-int.h:263
@ PG_BOOL_YES
Definition: libpq-int.h:265
@ PG_BOOL_NO
Definition: libpq-int.h:266
@ PG_BOOL_UNKNOWN
Definition: libpq-int.h:264
#define pglock_thread()
Definition: libpq-int.h:732
#define pgunlock_thread()
Definition: libpq-int.h:733

References conn, libpq_append_conn_error(), PG_BOOL_NO, PG_BOOL_UNKNOWN, PG_BOOL_YES, pglock_thread, and pgunlock_thread.

Referenced by pg_fe_run_oauth_flow_impl().

◆ oauth_json_array_end()

static JsonParseErrorType oauth_json_array_end ( void *  state)
static

Definition at line 570 of file fe-auth-oauth-curl.c.

571{
572 struct oauth_parse *ctx = state;
573
574 if (ctx->active)
575 {
576 /*
577 * Clear the target (which should be an array inside the top-level
578 * object). For this to be safe, no target arrays can contain other
579 * arrays; we check for that in the array_start callback.
580 */
581 if (ctx->nested != 2 || ctx->active->type != JSON_TOKEN_ARRAY_START)
582 {
583 Assert(false);
585 "internal error: found unexpected array end while parsing field '%s'",
586 ctx->active->name);
588 }
589
590 ctx->active = NULL;
591 }
592
593 --ctx->nested;
594 return JSON_SUCCESS;
595}
#define oauth_parse_set_error(ctx, fmt,...)
@ JSON_SEM_ACTION_FAILED
Definition: jsonapi.h:59
@ JSON_SUCCESS
Definition: jsonapi.h:36
@ JSON_TOKEN_ARRAY_START
Definition: jsonapi.h:24
const char * name
JsonTokenType type
const struct json_field * active
Definition: regguts.h:323

References oauth_parse::active, Assert(), JSON_SEM_ACTION_FAILED, JSON_SUCCESS, JSON_TOKEN_ARRAY_START, json_field::name, oauth_parse::nested, oauth_parse_set_error, 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 544 of file fe-auth-oauth-curl.c.

545{
546 struct oauth_parse *ctx = state;
547
548 if (!ctx->nested)
549 {
550 oauth_parse_set_error(ctx, "top-level element must be an object");
552 }
553
554 if (ctx->active)
555 {
557 /* The arrays we care about must not have arrays as values. */
558 || ctx->nested > 1)
559 {
562 }
563 }
564
565 ++ctx->nested;
566 return JSON_SUCCESS;
567}
static void report_type_mismatch(struct oauth_parse *ctx)

References oauth_parse::active, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, JSON_TOKEN_ARRAY_START, 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 521 of file fe-auth-oauth-curl.c.

522{
523 struct oauth_parse *ctx = state;
524
525 --ctx->nested;
526
527 /*
528 * All fields should be fully processed by the end of the top-level
529 * object.
530 */
531 if (!ctx->nested && ctx->active)
532 {
533 Assert(false);
535 "internal error: field '%s' still active at end of object",
536 ctx->active->name);
538 }
539
540 return JSON_SUCCESS;
541}

References oauth_parse::active, Assert(), JSON_SEM_ACTION_FAILED, JSON_SUCCESS, json_field::name, oauth_parse::nested, and oauth_parse_set_error.

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 466 of file fe-auth-oauth-curl.c.

467{
468 struct oauth_parse *ctx = state;
469
470 /* We care only about the top-level fields. */
471 if (ctx->nested == 1)
472 {
473 const struct json_field *field = ctx->fields;
474
475 /*
476 * We should never start parsing a new field while a previous one is
477 * still active.
478 */
479 if (ctx->active)
480 {
481 Assert(false);
483 "internal error: started field '%s' before field '%s' was finished",
484 name, ctx->active->name);
486 }
487
488 while (field->name)
489 {
490 if (strcmp(name, field->name) == 0)
491 {
492 ctx->active = field;
493 break;
494 }
495
496 ++field;
497 }
498
499 /*
500 * We don't allow duplicate field names; error out if the target has
501 * already been set.
502 */
503 if (ctx->active)
504 {
505 field = ctx->active;
506
507 if ((field->type == JSON_TOKEN_ARRAY_START && *field->target.array)
508 || (field->type != JSON_TOKEN_ARRAY_START && *field->target.scalar))
509 {
510 oauth_parse_set_error(ctx, "field \"%s\" is duplicated",
511 field->name);
513 }
514 }
515 }
516
517 return JSON_SUCCESS;
518}
union json_field::@185 target
struct curl_slist ** array
const struct json_field * fields
const char * name

References oauth_parse::active, json_field::array, Assert(), oauth_parse::fields, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, JSON_TOKEN_ARRAY_START, name, json_field::name, oauth_parse::nested, oauth_parse_set_error, json_field::scalar, json_field::target, 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 447 of file fe-auth-oauth-curl.c.

448{
449 struct oauth_parse *ctx = state;
450
451 if (ctx->active)
452 {
453 /*
454 * Currently, none of the fields we're interested in can be or contain
455 * objects, so we can reject this case outright.
456 */
459 }
460
461 ++ctx->nested;
462 return JSON_SUCCESS;
463}

References oauth_parse::active, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, oauth_parse::nested, 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 598 of file fe-auth-oauth-curl.c.

599{
600 struct oauth_parse *ctx = state;
601
602 if (!ctx->nested)
603 {
604 oauth_parse_set_error(ctx, "top-level element must be an object");
606 }
607
608 if (ctx->active)
609 {
610 const struct json_field *field = ctx->active;
611 JsonTokenType expected = field->type;
612
613 /* Make sure this matches what the active field expects. */
614 if (expected == JSON_TOKEN_ARRAY_START)
615 {
616 /* Are we actually inside an array? */
617 if (ctx->nested < 2)
618 {
621 }
622
623 /* Currently, arrays can only contain strings. */
624 expected = JSON_TOKEN_STRING;
625 }
626
627 if (type != expected)
628 {
631 }
632
633 if (field->type != JSON_TOKEN_ARRAY_START)
634 {
635 /* Ensure that we're parsing the top-level keys... */
636 if (ctx->nested != 1)
637 {
638 Assert(false);
640 "internal error: scalar target found at nesting level %d",
641 ctx->nested);
643 }
644
645 /* ...and that a result has not already been set. */
646 if (*field->target.scalar)
647 {
648 Assert(false);
650 "internal error: scalar field '%s' would be assigned twice",
651 ctx->active->name);
653 }
654
655 *field->target.scalar = strdup(token);
656 if (!*field->target.scalar)
657 return JSON_OUT_OF_MEMORY;
658
659 ctx->active = NULL;
660
661 return JSON_SUCCESS;
662 }
663 else
664 {
665 struct curl_slist *temp;
666
667 /* The target array should be inside the top-level object. */
668 if (ctx->nested != 2)
669 {
670 Assert(false);
672 "internal error: array member found at nesting level %d",
673 ctx->nested);
675 }
676
677 /* Note that curl_slist_append() makes a copy of the token. */
678 temp = curl_slist_append(*field->target.array, token);
679 if (!temp)
680 return JSON_OUT_OF_MEMORY;
681
682 *field->target.array = temp;
683 }
684 }
685 else
686 {
687 /* otherwise we just ignore it */
688 }
689
690 return JSON_SUCCESS;
691}
@ 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(), 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, report_type_mismatch(), json_field::scalar, json_field::target, 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 1069 of file fe-auth-oauth-curl.c.

1070{
1071 struct json_field fields[] = {
1072 {"access_token", JSON_TOKEN_STRING, {&tok->access_token}, PG_OAUTH_REQUIRED},
1073 {"token_type", JSON_TOKEN_STRING, {&tok->token_type}, PG_OAUTH_REQUIRED},
1074
1075 /*---
1076 * We currently have no use for the following OPTIONAL fields:
1077 *
1078 * - expires_in: This will be important for maintaining a token cache,
1079 * but we do not yet implement one.
1080 *
1081 * - refresh_token: Ditto.
1082 *
1083 * - scope: This is only sent when the authorization server sees fit to
1084 * change our scope request. It's not clear what we should do
1085 * about this; either it's been done as a matter of policy, or
1086 * the user has explicitly denied part of the authorization,
1087 * and either way the server-side validator is in a better
1088 * place to complain if the change isn't acceptable.
1089 */
1090
1091 {0},
1092 };
1093
1094 return parse_oauth_json(actx, fields);
1095}
static bool parse_oauth_json(struct async_ctx *actx, const struct json_field *fields)
#define PG_OAUTH_REQUIRED

References token::access_token, JSON_TOKEN_STRING, parse_oauth_json(), PG_OAUTH_REQUIRED, and token::token_type.

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 955 of file fe-auth-oauth-curl.c.

956{
957 struct json_field fields[] = {
958 {"device_code", JSON_TOKEN_STRING, {&authz->device_code}, PG_OAUTH_REQUIRED},
959 {"user_code", JSON_TOKEN_STRING, {&authz->user_code}, PG_OAUTH_REQUIRED},
960 {"verification_uri", JSON_TOKEN_STRING, {&authz->verification_uri}, PG_OAUTH_REQUIRED},
961 {"expires_in", JSON_TOKEN_NUMBER, {&authz->expires_in_str}, PG_OAUTH_REQUIRED},
962
963 /*
964 * Some services (Google, Azure) spell verification_uri differently.
965 * We accept either.
966 */
967 {"verification_url", JSON_TOKEN_STRING, {&authz->verification_uri}, PG_OAUTH_REQUIRED},
968
969 /*
970 * There is no evidence of verification_uri_complete being spelled
971 * with "url" instead with any service provider, so only support
972 * "uri".
973 */
974 {"verification_uri_complete", JSON_TOKEN_STRING, {&authz->verification_uri_complete}, PG_OAUTH_OPTIONAL},
975 {"interval", JSON_TOKEN_NUMBER, {&authz->interval_str}, PG_OAUTH_OPTIONAL},
976
977 {0},
978 };
979
980 if (!parse_oauth_json(actx, fields))
981 return false;
982
983 /*
984 * Parse our numeric fields. Lexing has already completed by this time, so
985 * we at least know they're valid JSON numbers.
986 */
987 if (authz->interval_str)
988 authz->interval = parse_interval(actx, authz->interval_str);
989 else
990 {
991 /*
992 * RFC 8628 specifies 5 seconds as the default value if the server
993 * doesn't provide an interval.
994 */
995 authz->interval = 5;
996 }
997
998 Assert(authz->expires_in_str); /* ensured by parse_oauth_json() */
999 authz->expires_in = parse_expires_in(actx, authz->expires_in_str);
1000
1001 return true;
1002}
static int parse_interval(struct async_ctx *actx, const char *interval_str)
#define PG_OAUTH_OPTIONAL
static int parse_expires_in(struct async_ctx *actx, const char *expires_in_str)
@ JSON_TOKEN_NUMBER
Definition: jsonapi.h:21

References Assert(), device_authz::device_code, device_authz::expires_in, device_authz::expires_in_str, 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 936 of file fe-auth-oauth-curl.c.

937{
938 double parsed;
939
940 parsed = parse_json_number(expires_in_str);
941 parsed = floor(parsed);
942
943 if (parsed >= INT_MAX)
944 return INT_MAX;
945 else if (parsed <= INT_MIN)
946 return INT_MIN;
947
948 return parsed;
949}
static double parse_json_number(const char *s)

References 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 910 of file fe-auth-oauth-curl.c.

911{
912 double parsed;
913
914 parsed = parse_json_number(interval_str);
915 parsed = ceil(parsed);
916
917 if (parsed < 1)
918 return actx->debugging ? 0 : 1;
919
920 else if (parsed >= INT_MAX)
921 return INT_MAX;
922
923 return parsed;
924}

References async_ctx::debugging, and parse_json_number().

Referenced by parse_device_authz().

◆ parse_json_number()

static double parse_json_number ( const char *  s)
static

Definition at line 875 of file fe-auth-oauth-curl.c.

876{
877 double parsed;
878 int cnt;
879
880 /*
881 * The JSON lexer has already validated the number, which is stricter than
882 * the %f format, so we should be good to use sscanf().
883 */
884 cnt = sscanf(s, "%lf", &parsed);
885
886 if (cnt != 1)
887 {
888 /*
889 * Either the lexer screwed up or our assumption above isn't true, and
890 * either way a developer needs to take a look.
891 */
892 Assert(false);
893 return 0;
894 }
895
896 return parsed;
897}

References Assert().

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 757 of file fe-auth-oauth-curl.c.

758{
759 PQExpBuffer resp = &actx->work_data;
760 JsonLexContext lex = {0};
761 JsonSemAction sem = {0};
763 struct oauth_parse ctx = {0};
764 bool success = false;
765
766 if (!check_content_type(actx, "application/json"))
767 return false;
768
769 if (strlen(resp->data) != resp->len)
770 {
771 actx_error(actx, "response contains embedded NULLs");
772 return false;
773 }
774
775 /*
776 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
777 * that up front.
778 */
779 if (pg_encoding_verifymbstr(PG_UTF8, resp->data, resp->len) != resp->len)
780 {
781 actx_error(actx, "response is not valid UTF-8");
782 return false;
783 }
784
785 makeJsonLexContextCstringLen(&lex, resp->data, resp->len, PG_UTF8, true);
786 setJsonLexContextOwnsTokens(&lex, true); /* must not leak on error */
787
788 ctx.errbuf = &actx->errbuf;
789 ctx.fields = fields;
790 sem.semstate = &ctx;
791
798
799 err = pg_parse_json(&lex, &sem);
800
801 if (err != JSON_SUCCESS)
802 {
803 /*
804 * For JSON_SEM_ACTION_FAILED, we've already written the error
805 * message. Other errors come directly from pg_parse_json(), already
806 * translated.
807 */
809 actx_error_str(actx, json_errdetail(err, &lex));
810
811 goto cleanup;
812 }
813
814 /* Check all required fields. */
815 while (fields->name)
816 {
817 if (fields->required
818 && !*fields->target.scalar
819 && !*fields->target.array)
820 {
821 actx_error(actx, "field \"%s\" is missing", fields->name);
822 goto cleanup;
823 }
824
825 fields++;
826 }
827
828 success = true;
829
830cleanup:
831 freeJsonLexContext(&lex);
832 return success;
833}
static JsonParseErrorType oauth_json_array_end(void *state)
static JsonParseErrorType oauth_json_object_field_start(void *state, char *name, bool isnull)
static JsonParseErrorType oauth_json_scalar(void *state, char *token, JsonTokenType type)
static JsonParseErrorType oauth_json_array_start(void *state)
static JsonParseErrorType oauth_json_object_end(void *state)
static bool check_content_type(struct async_ctx *actx, const char *type)
static JsonParseErrorType oauth_json_object_start(void *state)
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:2401
void freeJsonLexContext(JsonLexContext *lex)
Definition: jsonapi.c:687
JsonParseErrorType
Definition: jsonapi.h:35
@ PG_UTF8
Definition: pg_wchar.h:232
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
PQExpBuffer errbuf
static JsonSemAction sem
int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len)
Definition: wchar.c:2163

References actx_error, actx_error_str, json_field::array, JsonSemAction::array_end, JsonSemAction::array_start, check_content_type(), cleanup(), PQExpBufferData::data, err(), async_ctx::errbuf, oauth_parse::errbuf, oauth_parse::fields, freeJsonLexContext(), json_errdetail(), JSON_SEM_ACTION_FAILED, JSON_SUCCESS, PQExpBufferData::len, 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(), success, json_field::target, and async_ctx::work_data.

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 844 of file fe-auth-oauth-curl.c.

845{
846 struct json_field fields[] = {
849
850 /*----
851 * The following fields are technically REQUIRED, but we don't use
852 * them anywhere yet:
853 *
854 * - jwks_uri
855 * - response_types_supported
856 * - subject_types_supported
857 * - id_token_signing_alg_values_supported
858 */
859
860 {"device_authorization_endpoint", JSON_TOKEN_STRING, {&provider->device_authorization_endpoint}, PG_OAUTH_OPTIONAL},
861 {"grant_types_supported", JSON_TOKEN_ARRAY_START, {.array = &provider->grant_types_supported}, PG_OAUTH_OPTIONAL},
862
863 {0},
864 };
865
866 return parse_oauth_json(actx, fields);
867}

References provider::device_authorization_endpoint, 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 1009 of file fe-auth-oauth-curl.c.

1010{
1011 bool result;
1012 struct json_field fields[] = {
1013 {"error", JSON_TOKEN_STRING, {&err->error}, PG_OAUTH_REQUIRED},
1014
1015 {"error_description", JSON_TOKEN_STRING, {&err->error_description}, PG_OAUTH_OPTIONAL},
1016
1017 {0},
1018 };
1019
1020 result = parse_oauth_json(actx, fields);
1021
1022 /*
1023 * Since token errors are parsed during other active error paths, only
1024 * override the errctx if parsing explicitly fails.
1025 */
1026 if (!result)
1027 actx->errctx = "failed to parse token error response";
1028
1029 return result;
1030}

References err(), async_ctx::errctx, JSON_TOKEN_STRING, parse_oauth_json(), PG_OAUTH_OPTIONAL, and PG_OAUTH_REQUIRED.

Referenced by finish_device_authz(), and finish_token_request().

◆ pg_fe_cleanup_oauth_flow()

void pg_fe_cleanup_oauth_flow ( PGconn conn)

Definition at line 304 of file fe-auth-oauth-curl.c.

305{
307
308 if (state->async_ctx)
309 {
310 free_async_ctx(conn, state->async_ctx);
311 state->async_ctx = NULL;
312 }
313
315}
static void free_async_ctx(PGconn *conn, struct async_ctx *actx)
pgsocket altsock
Definition: libpq-int.h:530
void * sasl_state
Definition: libpq-int.h:600

References pg_conn::altsock, conn, free_async_ctx(), PGINVALID_SOCKET, and pg_conn::sasl_state.

Referenced by setup_token_request().

◆ pg_fe_run_oauth_flow()

PostgresPollingStatusType pg_fe_run_oauth_flow ( PGconn conn)

Definition at line 2850 of file fe-auth-oauth-curl.c.

2851{
2853#ifndef WIN32
2854 sigset_t osigset;
2855 bool sigpipe_pending;
2856 bool masked;
2857
2858 /*---
2859 * Ignore SIGPIPE on this thread during all Curl processing.
2860 *
2861 * Because we support multiple threads, we have to set up libcurl with
2862 * CURLOPT_NOSIGNAL, which disables its default global handling of
2863 * SIGPIPE. From the Curl docs:
2864 *
2865 * libcurl makes an effort to never cause such SIGPIPE signals to
2866 * trigger, but some operating systems have no way to avoid them and
2867 * even on those that have there are some corner cases when they may
2868 * still happen, contrary to our desire.
2869 *
2870 * Note that libcurl is also at the mercy of its DNS resolution and SSL
2871 * libraries; if any of them forget a MSG_NOSIGNAL then we're in trouble.
2872 * Modern platforms and libraries seem to get it right, so this is a
2873 * difficult corner case to exercise in practice, and unfortunately it's
2874 * not really clear whether it's necessary in all cases.
2875 */
2876 masked = (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0);
2877#endif
2878
2880
2881#ifndef WIN32
2882 if (masked)
2883 {
2884 /*
2885 * Undo the SIGPIPE mask. Assume we may have gotten EPIPE (we have no
2886 * way of knowing at this level).
2887 */
2888 pq_reset_sigpipe(&osigset, sigpipe_pending, true /* EPIPE, maybe */ );
2889 }
2890#endif
2891
2892 return result;
2893}
static PostgresPollingStatusType pg_fe_run_oauth_flow_impl(PGconn *conn)
void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
Definition: fe-secure.c:554
int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
Definition: fe-secure.c:504
PostgresPollingStatusType
Definition: libpq-fe.h:113

References conn, pg_fe_run_oauth_flow_impl(), pq_block_sigpipe(), and pq_reset_sigpipe().

Referenced by setup_token_request().

◆ pg_fe_run_oauth_flow_impl()

static PostgresPollingStatusType pg_fe_run_oauth_flow_impl ( PGconn conn)
static

Definition at line 2612 of file fe-auth-oauth-curl.c.

2613{
2615 struct async_ctx *actx;
2616
2617 if (!initialize_curl(conn))
2618 return PGRES_POLLING_FAILED;
2619
2620 if (!state->async_ctx)
2621 {
2622 /*
2623 * Create our asynchronous state, and hook it into the upper-level
2624 * OAuth state immediately, so any failures below won't leak the
2625 * context allocation.
2626 */
2627 actx = calloc(1, sizeof(*actx));
2628 if (!actx)
2629 {
2630 libpq_append_conn_error(conn, "out of memory");
2631 return PGRES_POLLING_FAILED;
2632 }
2633
2634 actx->mux = PGINVALID_SOCKET;
2635 actx->timerfd = -1;
2636
2637 /* Should we enable unsafe features? */
2639
2640 state->async_ctx = actx;
2641
2642 initPQExpBuffer(&actx->work_data);
2643 initPQExpBuffer(&actx->errbuf);
2644
2645 if (!setup_multiplexer(actx))
2646 goto error_return;
2647
2648 if (!setup_curl_handles(actx))
2649 goto error_return;
2650 }
2651
2652 actx = state->async_ctx;
2653
2654 do
2655 {
2656 /* By default, the multiplexer is the altsock. Reassign as desired. */
2657 conn->altsock = actx->mux;
2658
2659 switch (actx->step)
2660 {
2661 case OAUTH_STEP_INIT:
2662 break;
2663
2667 {
2669
2670 status = drive_request(actx);
2671
2672 if (status == PGRES_POLLING_FAILED)
2673 goto error_return;
2674 else if (status != PGRES_POLLING_OK)
2675 {
2676 /* not done yet */
2677 return status;
2678 }
2679
2680 break;
2681 }
2682
2684
2685 /*
2686 * The client application is supposed to wait until our timer
2687 * expires before calling PQconnectPoll() again, but that
2688 * might not happen. To avoid sending a token request early,
2689 * check the timer before continuing.
2690 */
2691 if (!timer_expired(actx))
2692 {
2693 conn->altsock = actx->timerfd;
2694 return PGRES_POLLING_READING;
2695 }
2696
2697 /* Disable the expired timer. */
2698 if (!set_timer(actx, -1))
2699 goto error_return;
2700
2701 break;
2702 }
2703
2704 /*
2705 * Each case here must ensure that actx->running is set while we're
2706 * waiting on some asynchronous work. Most cases rely on
2707 * start_request() to do that for them.
2708 */
2709 switch (actx->step)
2710 {
2711 case OAUTH_STEP_INIT:
2712 actx->errctx = "failed to fetch OpenID discovery document";
2714 goto error_return;
2715
2716 actx->step = OAUTH_STEP_DISCOVERY;
2717 break;
2718
2720 if (!finish_discovery(actx))
2721 goto error_return;
2722
2723 if (!check_issuer(actx, conn))
2724 goto error_return;
2725
2726 actx->errctx = "cannot run OAuth device authorization";
2727 if (!check_for_device_flow(actx))
2728 goto error_return;
2729
2730 actx->errctx = "failed to obtain device authorization";
2731 if (!start_device_authz(actx, conn))
2732 goto error_return;
2733
2735 break;
2736
2738 if (!finish_device_authz(actx))
2739 goto error_return;
2740
2741 actx->errctx = "failed to obtain access token";
2742 if (!start_token_request(actx, conn))
2743 goto error_return;
2744
2746 break;
2747
2750 goto error_return;
2751
2752 if (!actx->user_prompted)
2753 {
2754 /*
2755 * Now that we know the token endpoint isn't broken, give
2756 * the user the login instructions.
2757 */
2758 if (!prompt_user(actx, conn))
2759 goto error_return;
2760
2761 actx->user_prompted = true;
2762 }
2763
2764 if (conn->oauth_token)
2765 break; /* done! */
2766
2767 /*
2768 * Wait for the required interval before issuing the next
2769 * request.
2770 */
2771 if (!set_timer(actx, actx->authz.interval * 1000))
2772 goto error_return;
2773
2774 /*
2775 * No Curl requests are running, so we can simplify by having
2776 * the client wait directly on the timerfd rather than the
2777 * multiplexer.
2778 */
2779 conn->altsock = actx->timerfd;
2780
2782 actx->running = 1;
2783 break;
2784
2786 actx->errctx = "failed to obtain access token";
2787 if (!start_token_request(actx, conn))
2788 goto error_return;
2789
2791 break;
2792 }
2793
2794 /*
2795 * The vast majority of the time, if we don't have a token at this
2796 * point, actx->running will be set. But there are some corner cases
2797 * where we can immediately loop back around; see start_request().
2798 */
2799 } while (!conn->oauth_token && !actx->running);
2800
2801 /* If we've stored a token, we're done. Otherwise come back later. */
2803
2804error_return:
2805
2806 /*
2807 * Assemble the three parts of our error: context, body, and detail. See
2808 * also the documentation for struct async_ctx.
2809 */
2810 if (actx->errctx)
2811 {
2813 libpq_gettext(actx->errctx));
2815 }
2816
2817 if (PQExpBufferDataBroken(actx->errbuf))
2819 libpq_gettext("out of memory"));
2820 else
2822
2823 if (actx->curl_err[0])
2824 {
2825 size_t len;
2826
2828 " (libcurl: %s)", actx->curl_err);
2829
2830 /* Sometimes libcurl adds a newline to the error buffer. :( */
2832 if (len >= 2 && conn->errorMessage.data[len - 2] == '\n')
2833 {
2834 conn->errorMessage.data[len - 2] = ')';
2835 conn->errorMessage.data[len - 1] = '\0';
2837 }
2838 }
2839
2841
2842 return PGRES_POLLING_FAILED;
2843}
static bool setup_multiplexer(struct async_ctx *actx)
static bool start_token_request(struct async_ctx *actx, PGconn *conn)
static bool initialize_curl(PGconn *conn)
static bool set_timer(struct async_ctx *actx, long timeout)
static int timer_expired(struct async_ctx *actx)
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 bool start_discovery(struct async_ctx *actx, const char *discovery_uri)
static bool finish_device_authz(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)
static bool setup_curl_handles(struct async_ctx *actx)
bool oauth_unsafe_debugging_enabled(void)
#define calloc(a, b)
Definition: header.h:55
#define libpq_gettext(x)
Definition: libpq-int.h:934
#define PQExpBufferDataBroken(buf)
Definition: pqexpbuffer.h:67
enum OAuthStep step
char curl_err[CURL_ERROR_SIZE]
char * oauth_discovery_uri
Definition: libpq-int.h:443
char * oauth_token
Definition: libpq-int.h:448
PQExpBufferData errorMessage
Definition: libpq-int.h:671

References pg_conn::altsock, appendPQExpBuffer(), appendPQExpBufferStr(), async_ctx::authz, calloc, check_for_device_flow(), check_issuer(), conn, async_ctx::curl_err, PQExpBufferData::data, async_ctx::debugging, drive_request(), async_ctx::errbuf, async_ctx::errctx, pg_conn::errorMessage, finish_device_authz(), finish_discovery(), handle_token_response(), initialize_curl(), initPQExpBuffer(), device_authz::interval, PQExpBufferData::len, len, libpq_append_conn_error(), libpq_gettext, async_ctx::mux, pg_conn::oauth_discovery_uri, OAUTH_STEP_DEVICE_AUTHORIZATION, OAUTH_STEP_DISCOVERY, OAUTH_STEP_INIT, OAUTH_STEP_TOKEN_REQUEST, OAUTH_STEP_WAIT_INTERVAL, pg_conn::oauth_token, oauth_unsafe_debugging_enabled(), PGINVALID_SOCKET, PGRES_POLLING_FAILED, PGRES_POLLING_OK, PGRES_POLLING_READING, PQExpBufferDataBroken, prompt_user(), async_ctx::running, pg_conn::sasl_state, set_timer(), setup_curl_handles(), setup_multiplexer(), start_device_authz(), start_discovery(), start_token_request(), async_ctx::step, timer_expired(), async_ctx::timerfd, async_ctx::user_prompted, and async_ctx::work_data.

Referenced by pg_fe_run_oauth_flow().

◆ prompt_user()

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

Definition at line 2459 of file fe-auth-oauth-curl.c.

2460{
2461 int res;
2462 PGpromptOAuthDevice prompt = {
2464 .user_code = actx->authz.user_code,
2465 .verification_uri_complete = actx->authz.verification_uri_complete,
2466 .expires_in = actx->authz.expires_in,
2467 };
2468
2470
2471 if (!res)
2472 {
2473 /*
2474 * translator: The first %s is a URL for the user to visit in a
2475 * browser, and the second %s is a code to be copy-pasted there.
2476 */
2477 fprintf(stderr, libpq_gettext("Visit %s and enter the code: %s\n"),
2478 prompt.verification_uri, prompt.user_code);
2479 }
2480 else if (res < 0)
2481 {
2482 actx_error(actx, "device prompt failed");
2483 return false;
2484 }
2485
2486 return true;
2487}
PQauthDataHook_type PQauthDataHook
Definition: fe-auth.c:1586
@ PQAUTHDATA_PROMPT_OAUTH_DEVICE
Definition: libpq-fe.h:193
const char * verification_uri
Definition: libpq-fe.h:734
const char * user_code
Definition: libpq-fe.h:735

References actx_error, async_ctx::authz, conn, device_authz::expires_in, fprintf, libpq_gettext, PQAUTHDATA_PROMPT_OAUTH_DEVICE, PQauthDataHook, device_authz::user_code, _PGpromptOAuthDevice::user_code, device_authz::verification_uri, _PGpromptOAuthDevice::verification_uri, and device_authz::verification_uri_complete.

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 1037 of file fe-auth-oauth-curl.c.

1038{
1039 if (err->error_description)
1040 appendPQExpBuffer(&actx->errbuf, "%s ", err->error_description);
1041 else
1042 {
1043 /*
1044 * Try to get some more helpful detail into the error string. A 401
1045 * status in particular implies that the oauth_client_secret is
1046 * missing or wrong.
1047 */
1048 long response_code;
1049
1050 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, response_code = 0);
1051
1052 if (response_code == 401)
1053 {
1054 actx_error(actx, actx->used_basic_auth
1055 ? "provider rejected the oauth_client_secret"
1056 : "provider requires client authentication, and no oauth_client_secret is set");
1057 actx_error_str(actx, " ");
1058 }
1059 }
1060
1061 appendPQExpBuffer(&actx->errbuf, "(%s)", err->error);
1062}

References actx_error, actx_error_str, appendPQExpBuffer(), CHECK_GETINFO, err(), async_ctx::errbuf, and async_ctx::used_basic_auth.

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 1172 of file fe-auth-oauth-curl.c.

1174{
1175#ifdef HAVE_SYS_EPOLL_H
1176 struct async_ctx *actx = ctx;
1177 struct epoll_event ev = {0};
1178 int res;
1179 int op = EPOLL_CTL_ADD;
1180
1181 switch (what)
1182 {
1183 case CURL_POLL_IN:
1184 ev.events = EPOLLIN;
1185 break;
1186
1187 case CURL_POLL_OUT:
1188 ev.events = EPOLLOUT;
1189 break;
1190
1191 case CURL_POLL_INOUT:
1192 ev.events = EPOLLIN | EPOLLOUT;
1193 break;
1194
1195 case CURL_POLL_REMOVE:
1196 op = EPOLL_CTL_DEL;
1197 break;
1198
1199 default:
1200 actx_error(actx, "unknown libcurl socket operation: %d", what);
1201 return -1;
1202 }
1203
1204 res = epoll_ctl(actx->mux, op, socket, &ev);
1205 if (res < 0 && errno == EEXIST)
1206 {
1207 /* We already had this socket in the pollset. */
1208 op = EPOLL_CTL_MOD;
1209 res = epoll_ctl(actx->mux, op, socket, &ev);
1210 }
1211
1212 if (res < 0)
1213 {
1214 switch (op)
1215 {
1216 case EPOLL_CTL_ADD:
1217 actx_error(actx, "could not add to epoll set: %m");
1218 break;
1219
1220 case EPOLL_CTL_DEL:
1221 actx_error(actx, "could not delete from epoll set: %m");
1222 break;
1223
1224 default:
1225 actx_error(actx, "could not update epoll set: %m");
1226 }
1227
1228 return -1;
1229 }
1230
1231 return 0;
1232#endif
1233#ifdef HAVE_SYS_EVENT_H
1234 struct async_ctx *actx = ctx;
1235 struct kevent ev[2] = {{0}};
1236 struct kevent ev_out[2];
1237 struct timespec timeout = {0};
1238 int nev = 0;
1239 int res;
1240
1241 switch (what)
1242 {
1243 case CURL_POLL_IN:
1244 EV_SET(&ev[nev], socket, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, 0);
1245 nev++;
1246 break;
1247
1248 case CURL_POLL_OUT:
1249 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_ADD | EV_RECEIPT, 0, 0, 0);
1250 nev++;
1251 break;
1252
1253 case CURL_POLL_INOUT:
1254 EV_SET(&ev[nev], socket, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, 0);
1255 nev++;
1256 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_ADD | EV_RECEIPT, 0, 0, 0);
1257 nev++;
1258 break;
1259
1260 case CURL_POLL_REMOVE:
1261
1262 /*
1263 * We don't know which of these is currently registered, perhaps
1264 * both, so we try to remove both. This means we need to tolerate
1265 * ENOENT below.
1266 */
1267 EV_SET(&ev[nev], socket, EVFILT_READ, EV_DELETE | EV_RECEIPT, 0, 0, 0);
1268 nev++;
1269 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_DELETE | EV_RECEIPT, 0, 0, 0);
1270 nev++;
1271 break;
1272
1273 default:
1274 actx_error(actx, "unknown libcurl socket operation: %d", what);
1275 return -1;
1276 }
1277
1278 res = kevent(actx->mux, ev, nev, ev_out, lengthof(ev_out), &timeout);
1279 if (res < 0)
1280 {
1281 actx_error(actx, "could not modify kqueue: %m");
1282 return -1;
1283 }
1284
1285 /*
1286 * We can't use the simple errno version of kevent, because we need to
1287 * skip over ENOENT while still allowing a second change to be processed.
1288 * So we need a longer-form error checking loop.
1289 */
1290 for (int i = 0; i < res; ++i)
1291 {
1292 /*
1293 * EV_RECEIPT should guarantee one EV_ERROR result for every change,
1294 * whether successful or not. Failed entries contain a non-zero errno
1295 * in the data field.
1296 */
1297 Assert(ev_out[i].flags & EV_ERROR);
1298
1299 errno = ev_out[i].data;
1300 if (errno && errno != ENOENT)
1301 {
1302 switch (what)
1303 {
1304 case CURL_POLL_REMOVE:
1305 actx_error(actx, "could not delete from kqueue: %m");
1306 break;
1307 default:
1308 actx_error(actx, "could not add to kqueue: %m");
1309 }
1310 return -1;
1311 }
1312 }
1313
1314 return 0;
1315#endif
1316
1317 actx_error(actx, "libpq does not support multiplexer sockets on this platform");
1318 return -1;
1319}
#define lengthof(array)
Definition: c.h:759
#define socket(af, type, protocol)
Definition: win32_port.h:498

References actx_error, Assert(), i, lengthof, async_ctx::mux, and socket.

Referenced by setup_curl_handles().

◆ register_timer()

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

Definition at line 1458 of file fe-auth-oauth-curl.c.

1459{
1460 struct async_ctx *actx = ctx;
1461
1462 /*
1463 * There might be an optimization opportunity here: if timeout == 0, we
1464 * could signal drive_request to immediately call
1465 * curl_multi_socket_action, rather than returning all the way up the
1466 * stack only to come right back. But it's not clear that the additional
1467 * code complexity is worth it.
1468 */
1469 if (!set_timer(actx, timeout))
1470 return -1; /* actx_error already called */
1471
1472 return 0;
1473}

References set_timer().

Referenced by setup_curl_handles().

◆ report_type_mismatch()

static void report_type_mismatch ( struct oauth_parse ctx)
static

Definition at line 414 of file fe-auth-oauth-curl.c.

415{
416 char *msgfmt;
417
418 Assert(ctx->active);
419
420 /*
421 * At the moment, the only fields we're interested in are strings,
422 * numbers, and arrays of strings.
423 */
424 switch (ctx->active->type)
425 {
427 msgfmt = "field \"%s\" must be a string";
428 break;
429
431 msgfmt = "field \"%s\" must be a number";
432 break;
433
435 msgfmt = "field \"%s\" must be an array of strings";
436 break;
437
438 default:
439 Assert(false);
440 msgfmt = "field \"%s\" has unexpected type";
441 }
442
443 oauth_parse_set_error(ctx, msgfmt, ctx->active->name);
444}

References oauth_parse::active, Assert(), 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 1331 of file fe-auth-oauth-curl.c.

1332{
1333#if HAVE_SYS_EPOLL_H
1334 struct itimerspec spec = {0};
1335
1336 if (timeout < 0)
1337 {
1338 /* the zero itimerspec will disarm the timer below */
1339 }
1340 else if (timeout == 0)
1341 {
1342 /*
1343 * A zero timeout means libcurl wants us to call back immediately.
1344 * That's not technically an option for timerfd, but we can make the
1345 * timeout ridiculously short.
1346 */
1347 spec.it_value.tv_nsec = 1;
1348 }
1349 else
1350 {
1351 spec.it_value.tv_sec = timeout / 1000;
1352 spec.it_value.tv_nsec = (timeout % 1000) * 1000000;
1353 }
1354
1355 if (timerfd_settime(actx->timerfd, 0 /* no flags */ , &spec, NULL) < 0)
1356 {
1357 actx_error(actx, "setting timerfd to %ld: %m", timeout);
1358 return false;
1359 }
1360
1361 return true;
1362#endif
1363#ifdef HAVE_SYS_EVENT_H
1364 struct kevent ev;
1365
1366#ifdef __NetBSD__
1367
1368 /*
1369 * Work around NetBSD's rejection of zero timeouts (EINVAL), a bit like
1370 * timerfd above.
1371 */
1372 if (timeout == 0)
1373 timeout = 1;
1374#endif
1375
1376 /* Enable/disable the timer itself. */
1377 EV_SET(&ev, 1, EVFILT_TIMER, timeout < 0 ? EV_DELETE : (EV_ADD | EV_ONESHOT),
1378 0, timeout, 0);
1379 if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1380 {
1381 actx_error(actx, "setting kqueue timer to %ld: %m", timeout);
1382 return false;
1383 }
1384
1385 /*
1386 * Add/remove the timer to/from the mux. (In contrast with epoll, if we
1387 * allowed the timer to remain registered here after being disabled, the
1388 * mux queue would retain any previous stale timeout notifications and
1389 * remain readable.)
1390 */
1391 EV_SET(&ev, actx->timerfd, EVFILT_READ, timeout < 0 ? EV_DELETE : EV_ADD,
1392 0, 0, 0);
1393 if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1394 {
1395 actx_error(actx, "could not update timer on kqueue: %m");
1396 return false;
1397 }
1398
1399 return true;
1400#endif
1401
1402 actx_error(actx, "libpq does not support timers on this platform");
1403 return false;
1404}

References actx_error, async_ctx::mux, and async_ctx::timerfd.

Referenced by 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 1565 of file fe-auth-oauth-curl.c.

1566{
1567 /*
1568 * Create our multi handle. This encapsulates the entire conversation with
1569 * libcurl for this connection.
1570 */
1571 actx->curlm = curl_multi_init();
1572 if (!actx->curlm)
1573 {
1574 /* We don't get a lot of feedback on the failure reason. */
1575 actx_error(actx, "failed to create libcurl multi handle");
1576 return false;
1577 }
1578
1579 /*
1580 * The multi handle tells us what to wait on using two callbacks. These
1581 * will manipulate actx->mux as needed.
1582 */
1583 CHECK_MSETOPT(actx, CURLMOPT_SOCKETFUNCTION, register_socket, return false);
1584 CHECK_MSETOPT(actx, CURLMOPT_SOCKETDATA, actx, return false);
1585 CHECK_MSETOPT(actx, CURLMOPT_TIMERFUNCTION, register_timer, return false);
1586 CHECK_MSETOPT(actx, CURLMOPT_TIMERDATA, actx, return false);
1587
1588 /*
1589 * Set up an easy handle. All of our requests are made serially, so we
1590 * only ever need to keep track of one.
1591 */
1592 actx->curl = curl_easy_init();
1593 if (!actx->curl)
1594 {
1595 actx_error(actx, "failed to create libcurl handle");
1596 return false;
1597 }
1598
1599 /*
1600 * Multi-threaded applications must set CURLOPT_NOSIGNAL. This requires us
1601 * to handle the possibility of SIGPIPE ourselves using pq_block_sigpipe;
1602 * see pg_fe_run_oauth_flow().
1603 *
1604 * NB: If libcurl is not built against a friendly DNS resolver (c-ares or
1605 * threaded), setting this option prevents DNS lookups from timing out
1606 * correctly. We warn about this situation at configure time.
1607 *
1608 * TODO: Perhaps there's a clever way to warn the user about synchronous
1609 * DNS at runtime too? It's not immediately clear how to do that in a
1610 * helpful way: for many standard single-threaded use cases, the user
1611 * might not care at all, so spraying warnings to stderr would probably do
1612 * more harm than good.
1613 */
1614 CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L, return false);
1615
1616 if (actx->debugging)
1617 {
1618 /*
1619 * Set a callback for retrieving error information from libcurl, the
1620 * function only takes effect when CURLOPT_VERBOSE has been set so
1621 * make sure the order is kept.
1622 */
1623 CHECK_SETOPT(actx, CURLOPT_DEBUGFUNCTION, debug_callback, return false);
1624 CHECK_SETOPT(actx, CURLOPT_VERBOSE, 1L, return false);
1625 }
1626
1627 CHECK_SETOPT(actx, CURLOPT_ERRORBUFFER, actx->curl_err, return false);
1628
1629 /*
1630 * Only HTTPS is allowed. (Debug mode additionally allows HTTP; this is
1631 * intended for testing only.)
1632 *
1633 * There's a bit of unfortunate complexity around the choice of
1634 * CURLoption. CURLOPT_PROTOCOLS is deprecated in modern Curls, but its
1635 * replacement didn't show up until relatively recently.
1636 */
1637 {
1638#if CURL_AT_LEAST_VERSION(7, 85, 0)
1639 const CURLoption popt = CURLOPT_PROTOCOLS_STR;
1640 const char *protos = "https";
1641 const char *const unsafe = "https,http";
1642#else
1643 const CURLoption popt = CURLOPT_PROTOCOLS;
1644 long protos = CURLPROTO_HTTPS;
1645 const long unsafe = CURLPROTO_HTTPS | CURLPROTO_HTTP;
1646#endif
1647
1648 if (actx->debugging)
1649 protos = unsafe;
1650
1651 CHECK_SETOPT(actx, popt, protos, return false);
1652 }
1653
1654 /*
1655 * If we're in debug mode, allow the developer to change the trusted CA
1656 * list. For now, this is not something we expose outside of the UNSAFE
1657 * mode, because it's not clear that it's useful in production: both libpq
1658 * and the user's browser must trust the same authorization servers for
1659 * the flow to work at all, so any changes to the roots are likely to be
1660 * done system-wide.
1661 */
1662 if (actx->debugging)
1663 {
1664 const char *env;
1665
1666 if ((env = getenv("PGOAUTHCAFILE")) != NULL)
1667 CHECK_SETOPT(actx, CURLOPT_CAINFO, env, return false);
1668 }
1669
1670 /*
1671 * Suppress the Accept header to make our request as minimal as possible.
1672 * (Ideally we would set it to "application/json" instead, but OpenID is
1673 * pretty strict when it comes to provider behavior, so we have to check
1674 * what comes back anyway.)
1675 */
1676 actx->headers = curl_slist_append(actx->headers, "Accept:");
1677 if (actx->headers == NULL)
1678 {
1679 actx_error(actx, "out of memory");
1680 return false;
1681 }
1682 CHECK_SETOPT(actx, CURLOPT_HTTPHEADER, actx->headers, return false);
1683
1684 return true;
1685}
static int register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx, void *socketp)
#define CHECK_MSETOPT(ACTX, OPT, VAL, FAILACTION)
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)

References actx_error, CHECK_MSETOPT, CHECK_SETOPT, async_ctx::curl, async_ctx::curl_err, async_ctx::curlm, debug_callback(), async_ctx::debugging, async_ctx::headers, register_socket(), and register_timer().

Referenced by pg_fe_run_oauth_flow_impl().

◆ setup_multiplexer()

static bool setup_multiplexer ( struct async_ctx actx)
static

Definition at line 1111 of file fe-auth-oauth-curl.c.

1112{
1113#ifdef HAVE_SYS_EPOLL_H
1114 struct epoll_event ev = {.events = EPOLLIN};
1115
1116 actx->mux = epoll_create1(EPOLL_CLOEXEC);
1117 if (actx->mux < 0)
1118 {
1119 actx_error(actx, "failed to create epoll set: %m");
1120 return false;
1121 }
1122
1123 actx->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
1124 if (actx->timerfd < 0)
1125 {
1126 actx_error(actx, "failed to create timerfd: %m");
1127 return false;
1128 }
1129
1130 if (epoll_ctl(actx->mux, EPOLL_CTL_ADD, actx->timerfd, &ev) < 0)
1131 {
1132 actx_error(actx, "failed to add timerfd to epoll set: %m");
1133 return false;
1134 }
1135
1136 return true;
1137#endif
1138#ifdef HAVE_SYS_EVENT_H
1139 actx->mux = kqueue();
1140 if (actx->mux < 0)
1141 {
1142 /*- translator: the term "kqueue" (kernel queue) should not be translated */
1143 actx_error(actx, "failed to create kqueue: %m");
1144 return false;
1145 }
1146
1147 /*
1148 * Originally, we set EVFILT_TIMER directly on the top-level multiplexer.
1149 * This makes it difficult to implement timer_expired(), though, so now we
1150 * set EVFILT_TIMER on a separate actx->timerfd, which is chained to
1151 * actx->mux while the timer is active.
1152 */
1153 actx->timerfd = kqueue();
1154 if (actx->timerfd < 0)
1155 {
1156 actx_error(actx, "failed to create timer kqueue: %m");
1157 return false;
1158 }
1159
1160 return true;
1161#endif
1162
1163 actx_error(actx, "libpq does not support the Device Authorization flow on this platform");
1164 return false;
1165}

References actx_error, async_ctx::mux, and async_ctx::timerfd.

Referenced by pg_fe_run_oauth_flow_impl().

◆ start_device_authz()

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

Definition at line 2232 of file fe-auth-oauth-curl.c.

2233{
2234 const char *device_authz_uri = actx->provider.device_authorization_endpoint;
2235 PQExpBuffer work_buffer = &actx->work_data;
2236
2237 Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
2238 Assert(device_authz_uri); /* ensured by check_for_device_flow() */
2239
2240 /* Construct our request body. */
2241 resetPQExpBuffer(work_buffer);
2242 if (conn->oauth_scope && conn->oauth_scope[0])
2243 build_urlencoded(work_buffer, "scope", conn->oauth_scope);
2244
2245 if (!add_client_identification(actx, work_buffer, conn))
2246 return false;
2247
2248 if (PQExpBufferBroken(work_buffer))
2249 {
2250 actx_error(actx, "out of memory");
2251 return false;
2252 }
2253
2254 /* Make our request. */
2255 CHECK_SETOPT(actx, CURLOPT_URL, device_authz_uri, return false);
2256 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2257
2258 return start_request(actx);
2259}
static bool add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
static bool start_request(struct async_ctx *actx)
void resetPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:146
char * oauth_scope
Definition: libpq-int.h:447

References actx_error, add_client_identification(), Assert(), build_urlencoded(), CHECK_SETOPT, conn, PQExpBufferData::data, provider::device_authorization_endpoint, pg_conn::oauth_client_id, pg_conn::oauth_scope, PQExpBufferBroken, async_ctx::provider, resetPQExpBuffer(), start_request(), and async_ctx::work_data.

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 1974 of file fe-auth-oauth-curl.c.

1975{
1976 CHECK_SETOPT(actx, CURLOPT_HTTPGET, 1L, return false);
1977 CHECK_SETOPT(actx, CURLOPT_URL, discovery_uri, return false);
1978
1979 return start_request(actx);
1980}

References CHECK_SETOPT, 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 1738 of file fe-auth-oauth-curl.c.

1739{
1740 CURLMcode err;
1741
1743 CHECK_SETOPT(actx, CURLOPT_WRITEFUNCTION, append_data, return false);
1744 CHECK_SETOPT(actx, CURLOPT_WRITEDATA, actx, return false);
1745
1746 err = curl_multi_add_handle(actx->curlm, actx->curl);
1747 if (err)
1748 {
1749 actx_error(actx, "failed to queue HTTP request: %s",
1750 curl_multi_strerror(err));
1751 return false;
1752 }
1753
1754 /*
1755 * actx->running tracks the number of running handles, so we can
1756 * immediately call back if no waiting is needed.
1757 *
1758 * Even though this is nominally an asynchronous process, there are some
1759 * operations that can synchronously fail by this point (e.g. connections
1760 * to closed local ports) or even synchronously succeed if the stars align
1761 * (all the libcurl connection caches hit and the server is fast).
1762 */
1763 err = curl_multi_socket_action(actx->curlm, CURL_SOCKET_TIMEOUT, 0, &actx->running);
1764 if (err)
1765 {
1766 actx_error(actx, "asynchronous HTTP request failed: %s",
1767 curl_multi_strerror(err));
1768 return false;
1769 }
1770
1771 return true;
1772}
static size_t append_data(char *buf, size_t size, size_t nmemb, void *userdata)

References actx_error, append_data(), CHECK_SETOPT, async_ctx::curl, async_ctx::curlm, err(), resetPQExpBuffer(), async_ctx::running, and async_ctx::work_data.

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 2319 of file fe-auth-oauth-curl.c.

2320{
2321 const char *token_uri = actx->provider.token_endpoint;
2322 const char *device_code = actx->authz.device_code;
2323 PQExpBuffer work_buffer = &actx->work_data;
2324
2325 Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
2326 Assert(token_uri); /* ensured by parse_provider() */
2327 Assert(device_code); /* ensured by parse_device_authz() */
2328
2329 /* Construct our request body. */
2330 resetPQExpBuffer(work_buffer);
2331 build_urlencoded(work_buffer, "device_code", device_code);
2332 build_urlencoded(work_buffer, "grant_type", OAUTH_GRANT_TYPE_DEVICE_CODE);
2333
2334 if (!add_client_identification(actx, work_buffer, conn))
2335 return false;
2336
2337 if (PQExpBufferBroken(work_buffer))
2338 {
2339 actx_error(actx, "out of memory");
2340 return false;
2341 }
2342
2343 /* Make our request. */
2344 CHECK_SETOPT(actx, CURLOPT_URL, token_uri, return false);
2345 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2346
2347 return start_request(actx);
2348}
#define OAUTH_GRANT_TYPE_DEVICE_CODE

References actx_error, add_client_identification(), Assert(), async_ctx::authz, build_urlencoded(), CHECK_SETOPT, conn, PQExpBufferData::data, device_authz::device_code, pg_conn::oauth_client_id, OAUTH_GRANT_TYPE_DEVICE_CODE, PQExpBufferBroken, async_ctx::provider, resetPQExpBuffer(), start_request(), provider::token_endpoint, and async_ctx::work_data.

Referenced by pg_fe_run_oauth_flow_impl().

◆ timer_expired()

static int timer_expired ( struct async_ctx actx)
static

Definition at line 1412 of file fe-auth-oauth-curl.c.

1413{
1414#if HAVE_SYS_EPOLL_H
1415 struct itimerspec spec = {0};
1416
1417 if (timerfd_gettime(actx->timerfd, &spec) < 0)
1418 {
1419 actx_error(actx, "getting timerfd value: %m");
1420 return -1;
1421 }
1422
1423 /*
1424 * This implementation assumes we're using single-shot timers. If you
1425 * change to using intervals, you'll need to reimplement this function
1426 * too, possibly with the read() or select() interfaces for timerfd.
1427 */
1428 Assert(spec.it_interval.tv_sec == 0
1429 && spec.it_interval.tv_nsec == 0);
1430
1431 /* If the remaining time to expiration is zero, we're done. */
1432 return (spec.it_value.tv_sec == 0
1433 && spec.it_value.tv_nsec == 0);
1434#endif
1435#ifdef HAVE_SYS_EVENT_H
1436 int res;
1437
1438 /* Is the timer queue ready? */
1439 res = PQsocketPoll(actx->timerfd, 1 /* forRead */ , 0, 0);
1440 if (res < 0)
1441 {
1442 actx_error(actx, "checking kqueue for timeout: %m");
1443 return -1;
1444 }
1445
1446 return (res > 0);
1447#endif
1448
1449 actx_error(actx, "libpq does not support timers on this platform");
1450 return -1;
1451}
int PQsocketPoll(int sock, int forRead, int forWrite, pg_usec_time_t end_time)
Definition: fe-misc.c:1115

References actx_error, Assert(), PQsocketPoll(), and async_ctx::timerfd.

Referenced by pg_fe_run_oauth_flow_impl().

◆ urlencode()

static char * urlencode ( const char *  s)
static

Definition at line 1930 of file fe-auth-oauth-curl.c.

1931{
1933
1936
1937 return PQExpBufferDataBroken(buf) ? NULL : buf.data;
1938}

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

Referenced by add_client_identification().