PostgreSQL Source Code git master
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-oauth.h"
#include "mb/pg_wchar.h"
#include "oauth-curl.h"
#include "libpq-int.h"
Include dependency graph for 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 conn_errorMessage(CONN)   (&CONN->errorMessage)
 
#define conn_oauth_client_id(CONN)   (CONN->oauth_client_id)
 
#define conn_oauth_client_secret(CONN)   (CONN->oauth_client_secret)
 
#define conn_oauth_discovery_uri(CONN)   (CONN->oauth_discovery_uri)
 
#define conn_oauth_issuer_id(CONN)   (CONN->oauth_issuer_id)
 
#define conn_oauth_scope(CONN)   (CONN->oauth_scope)
 
#define conn_sasl_state(CONN)   (CONN->sasl_state)
 
#define set_conn_altsock(CONN, VAL)   do { CONN->altsock = VAL; } while (0)
 
#define set_conn_oauth_token(CONN, VAL)   do { CONN->oauth_token = VAL; } while (0)
 
#define MAX_OAUTH_RESPONSE_SIZE   (256 * 1024)
 
#define MAX_OAUTH_NESTING_LEVEL   16
 
#define actx_error(ACTX, FMT, ...)    appendPQExpBuffer(&(ACTX)->errbuf, libpq_gettext(FMT), ##__VA_ARGS__)
 
#define actx_error_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 373 of file oauth-curl.c.

◆ actx_error_str

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

Definition at line 376 of file 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 406 of file 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 384 of file 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 395 of file oauth-curl.c.

◆ conn_errorMessage

#define conn_errorMessage (   CONN)    (&CONN->errorMessage)

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

◆ conn_oauth_client_id

#define conn_oauth_client_id (   CONN)    (CONN->oauth_client_id)

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

◆ conn_oauth_client_secret

#define conn_oauth_client_secret (   CONN)    (CONN->oauth_client_secret)

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

◆ conn_oauth_discovery_uri

#define conn_oauth_discovery_uri (   CONN)    (CONN->oauth_discovery_uri)

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

◆ conn_oauth_issuer_id

#define conn_oauth_issuer_id (   CONN)    (CONN->oauth_issuer_id)

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

◆ conn_oauth_scope

#define conn_oauth_scope (   CONN)    (CONN->oauth_scope)

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

◆ conn_sasl_state

#define conn_sasl_state (   CONN)    (CONN->sasl_state)

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

◆ CURL_IGNORE_DEPRECATION

#define CURL_IGNORE_DEPRECATION (   x)    x

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

◆ HTTPS_SCHEME

#define HTTPS_SCHEME   "https://"

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

◆ MAX_OAUTH_NESTING_LEVEL

#define MAX_OAUTH_NESTING_LEVEL   16

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

◆ MAX_OAUTH_RESPONSE_SIZE

#define MAX_OAUTH_RESPONSE_SIZE   (256 * 1024)

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

◆ OAUTH_GRANT_TYPE_DEVICE_CODE

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

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

◆ oauth_parse_set_error

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

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

◆ PG_OAUTH_OPTIONAL

#define PG_OAUTH_OPTIONAL   false

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

◆ PG_OAUTH_REQUIRED

#define PG_OAUTH_REQUIRED   true

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

◆ set_conn_altsock

#define set_conn_altsock (   CONN,
  VAL 
)    do { CONN->altsock = VAL; } while (0)

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

◆ set_conn_oauth_token

#define set_conn_oauth_token (   CONN,
  VAL 
)    do { CONN->oauth_token = VAL; } while (0)

Definition at line 62 of file 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 214 of file oauth-curl.c.

215{
216 OAUTH_STEP_INIT = 0,
221};
@ OAUTH_STEP_DEVICE_AUTHORIZATION
Definition: oauth-curl.c:218
@ OAUTH_STEP_WAIT_INTERVAL
Definition: oauth-curl.c:220
@ OAUTH_STEP_INIT
Definition: oauth-curl.c:216
@ OAUTH_STEP_DISCOVERY
Definition: oauth-curl.c:217
@ OAUTH_STEP_TOKEN_REQUEST
Definition: oauth-curl.c:219

Function Documentation

◆ add_client_identification()

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

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

2229{
2230 const char *oauth_client_id = conn_oauth_client_id(conn);
2231 const char *oauth_client_secret = conn_oauth_client_secret(conn);
2232
2233 bool success = false;
2234 char *username = NULL;
2235 char *password = NULL;
2236
2237 if (oauth_client_secret) /* Zero-length secrets are permitted! */
2238 {
2239 /*----
2240 * Use HTTP Basic auth to send the client_id and secret. Per RFC 6749,
2241 * Sec. 2.3.1,
2242 *
2243 * Including the client credentials in the request-body using the
2244 * two parameters is NOT RECOMMENDED and SHOULD be limited to
2245 * clients unable to directly utilize the HTTP Basic authentication
2246 * scheme (or other password-based HTTP authentication schemes).
2247 *
2248 * Additionally:
2249 *
2250 * The client identifier is encoded using the
2251 * "application/x-www-form-urlencoded" encoding algorithm per Appendix
2252 * B, and the encoded value is used as the username; the client
2253 * password is encoded using the same algorithm and used as the
2254 * password.
2255 *
2256 * (Appendix B modifies application/x-www-form-urlencoded by requiring
2257 * an initial UTF-8 encoding step. Since the client ID and secret must
2258 * both be 7-bit ASCII -- RFC 6749 Appendix A -- we don't worry about
2259 * that in this function.)
2260 *
2261 * client_id is not added to the request body in this case. Not only
2262 * would it be redundant, but some providers in the wild (e.g. Okta)
2263 * refuse to accept it.
2264 */
2265 username = urlencode(oauth_client_id);
2266 password = urlencode(oauth_client_secret);
2267
2268 if (!username || !password)
2269 {
2270 actx_error(actx, "out of memory");
2271 goto cleanup;
2272 }
2273
2274 CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_BASIC, goto cleanup);
2275 CHECK_SETOPT(actx, CURLOPT_USERNAME, username, goto cleanup);
2276 CHECK_SETOPT(actx, CURLOPT_PASSWORD, password, goto cleanup);
2277
2278 actx->used_basic_auth = true;
2279 }
2280 else
2281 {
2282 /*
2283 * If we're not otherwise authenticating, client_id is REQUIRED in the
2284 * request body.
2285 */
2286 build_urlencoded(reqbody, "client_id", oauth_client_id);
2287
2288 CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, goto cleanup);
2289 actx->used_basic_auth = false;
2290 }
2291
2292 success = true;
2293
2294cleanup:
2295 free(username);
2296 free(password);
2297
2298 return success;
2299}
static void cleanup(void)
Definition: bootstrap.c:713
#define free(a)
Definition: header.h:65
static bool success
Definition: initdb.c:187
static char * username
Definition: initdb.c:153
static char * urlencode(const char *s)
Definition: oauth-curl.c:2006
static void build_urlencoded(PQExpBuffer buf, const char *key, const char *value)
Definition: oauth-curl.c:2021
#define CHECK_SETOPT(ACTX, OPT, VAL, FAILACTION)
Definition: oauth-curl.c:395
#define conn_oauth_client_id(CONN)
Definition: oauth-curl.c:54
#define conn_oauth_client_secret(CONN)
Definition: oauth-curl.c:55
#define actx_error(ACTX, FMT,...)
Definition: oauth-curl.c:373
static char * password
Definition: streamutil.c:51
PGconn * conn
Definition: streamutil.c:52
bool used_basic_auth
Definition: oauth-curl.c:279

References actx_error, build_urlencoded(), CHECK_SETOPT, cleanup(), conn, conn_oauth_client_id, conn_oauth_client_secret, free, 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 1774 of file oauth-curl.c.

1775{
1776 struct async_ctx *actx = userdata;
1777 PQExpBuffer resp = &actx->work_data;
1778 size_t len = size * nmemb;
1779
1780 /* In case we receive data over the threshold, abort the transfer */
1781 if ((resp->len + len) > MAX_OAUTH_RESPONSE_SIZE)
1782 {
1783 actx_error(actx, "response is too large");
1784 return 0;
1785 }
1786
1787 /* The data passed from libcurl is not null-terminated */
1789
1790 /*
1791 * Signal an error in order to abort the transfer in case we ran out of
1792 * memory in accepting the data.
1793 */
1794 if (PQExpBufferBroken(resp))
1795 {
1796 actx_error(actx, "out of memory");
1797 return 0;
1798 }
1799
1800 return len;
1801}
#define MAX_OAUTH_RESPONSE_SIZE
Definition: oauth-curl.c:83
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
Definition: oauth-curl.c:242

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

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

◆ append_urlencoded()

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

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

1964{
1965 char *escaped;
1966 char *haystack;
1967 char *match;
1968
1969 /* The first parameter to curl_easy_escape is deprecated by Curl */
1970 escaped = curl_easy_escape(NULL, s, 0);
1971 if (!escaped)
1972 {
1973 termPQExpBuffer(buf); /* mark the buffer broken */
1974 return;
1975 }
1976
1977 /*
1978 * curl_easy_escape() almost does what we want, but we need the
1979 * query-specific flavor which uses '+' instead of '%20' for spaces. The
1980 * Curl command-line tool does this with a simple search-and-replace, so
1981 * follow its lead.
1982 */
1983 haystack = escaped;
1984
1985 while ((match = strstr(haystack, "%20")) != NULL)
1986 {
1987 /* Append the unmatched portion, followed by the plus sign. */
1988 appendBinaryPQExpBuffer(buf, haystack, match - haystack);
1990
1991 /* Keep searching after the match. */
1992 haystack = match + 3 /* strlen("%20") */ ;
1993 }
1994
1995 /* Push the remainder of the string onto the buffer. */
1996 appendPQExpBufferStr(buf, haystack);
1997
1998 curl_free(escaped);
1999}
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 2021 of file oauth-curl.c.

2022{
2023 if (buf->len)
2025
2029}
static struct @165 value
static void append_urlencoded(PQExpBuffer buf, const char *s)
Definition: oauth-curl.c:1963

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 760 of file oauth-curl.c.

761{
762 const size_t type_len = strlen(type);
763 char *content_type;
764
765 CHECK_GETINFO(actx, CURLINFO_CONTENT_TYPE, &content_type, return false);
766
767 if (!content_type)
768 {
769 actx_error(actx, "no content type was provided");
770 return false;
771 }
772
773 /*
774 * We need to perform a length limited comparison and not compare the
775 * whole string.
776 */
777 if (pg_strncasecmp(content_type, type, type_len) != 0)
778 goto fail;
779
780 /* On an exact match, we're done. */
781 Assert(strlen(content_type) >= type_len);
782 if (content_type[type_len] == '\0')
783 return true;
784
785 /*
786 * Only a semicolon (optionally preceded by HTTP optional whitespace) is
787 * acceptable after the prefix we checked. This marks the start of media
788 * type parameters, which we currently have no use for.
789 */
790 for (size_t i = type_len; content_type[i]; ++i)
791 {
792 switch (content_type[i])
793 {
794 case ';':
795 return true; /* success! */
796
797 case ' ':
798 case '\t':
799 /* HTTP optional whitespace allows only spaces and htabs. */
800 break;
801
802 default:
803 goto fail;
804 }
805 }
806
807fail:
808 actx_error(actx, "unexpected content type: \"%s\"", content_type);
809 return false;
810}
Assert(PointerIsAligned(start, uint64))
int i
Definition: isn.c:77
#define CHECK_GETINFO(ACTX, INFO, OUT, FAILACTION)
Definition: oauth-curl.c:406
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 2169 of file oauth-curl.c.

2170{
2171 const struct provider *provider = &actx->provider;
2172
2173 Assert(provider->issuer); /* ensured by parse_provider() */
2174 Assert(provider->token_endpoint); /* ensured by parse_provider() */
2175
2177 {
2178 actx_error(actx,
2179 "issuer \"%s\" does not provide a device authorization endpoint",
2180 provider->issuer);
2181 return false;
2182 }
2183
2184 /*
2185 * The original implementation checked that OAUTH_GRANT_TYPE_DEVICE_CODE
2186 * was present in the discovery document's grant_types_supported list. MS
2187 * Entra does not advertise this grant type, though, and since it doesn't
2188 * make sense to stand up a device_authorization_endpoint without also
2189 * accepting device codes at the token_endpoint, that's the only thing we
2190 * currently require.
2191 */
2192
2193 /*
2194 * Although libcurl will fail later if the URL contains an unsupported
2195 * scheme, that error message is going to be a bit opaque. This is a
2196 * decent time to bail out if we're not using HTTPS for the endpoints
2197 * we'll use for the flow.
2198 */
2199 if (!actx->debugging)
2200 {
2202 HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
2203 {
2204 actx_error(actx,
2205 "device authorization endpoint \"%s\" must use HTTPS",
2207 return false;
2208 }
2209
2211 HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
2212 {
2213 actx_error(actx,
2214 "token endpoint \"%s\" must use HTTPS",
2216 return false;
2217 }
2218 }
2219
2220 return true;
2221}
#define HTTPS_SCHEME
Definition: oauth-curl.c:2160
bool debugging
Definition: oauth-curl.c:280
struct provider provider
Definition: oauth-curl.c:274
char * device_authorization_endpoint
Definition: oauth-curl.c:118
char * issuer
Definition: oauth-curl.c:116
char * token_endpoint
Definition: oauth-curl.c:117

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 2124 of file oauth-curl.c.

2125{
2126 const struct provider *provider = &actx->provider;
2127 const char *oauth_issuer_id = conn_oauth_issuer_id(conn);
2128
2129 Assert(oauth_issuer_id); /* ensured by setup_oauth_parameters() */
2130 Assert(provider->issuer); /* ensured by parse_provider() */
2131
2132 /*---
2133 * We require strict equality for issuer identifiers -- no path or case
2134 * normalization, no substitution of default ports and schemes, etc. This
2135 * is done to match the rules in OIDC Discovery Sec. 4.3 for config
2136 * validation:
2137 *
2138 * The issuer value returned MUST be identical to the Issuer URL that
2139 * was used as the prefix to /.well-known/openid-configuration to
2140 * retrieve the configuration information.
2141 *
2142 * as well as the rules set out in RFC 9207 for avoiding mix-up attacks:
2143 *
2144 * Clients MUST then [...] compare the result to the issuer identifier
2145 * of the authorization server where the authorization request was
2146 * sent to. This comparison MUST use simple string comparison as defined
2147 * in Section 6.2.1 of [RFC3986].
2148 */
2149 if (strcmp(oauth_issuer_id, provider->issuer) != 0)
2150 {
2151 actx_error(actx,
2152 "the issuer identifier (%s) does not match oauth_issuer (%s)",
2153 provider->issuer, oauth_issuer_id);
2154 return false;
2155 }
2156
2157 return true;
2158}
#define conn_oauth_issuer_id(CONN)
Definition: oauth-curl.c:57

References actx_error, Assert(), conn, conn_oauth_issuer_id, provider::issuer, 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 1558 of file oauth-curl.c.

1560{
1561 const char *prefix;
1562 bool printed_prefix = false;
1564
1565 /* Prefixes are modeled off of the default libcurl debug output. */
1566 switch (type)
1567 {
1568 case CURLINFO_TEXT:
1569 prefix = "*";
1570 break;
1571
1572 case CURLINFO_HEADER_IN: /* fall through */
1573 case CURLINFO_DATA_IN:
1574 prefix = "<";
1575 break;
1576
1577 case CURLINFO_HEADER_OUT: /* fall through */
1578 case CURLINFO_DATA_OUT:
1579 prefix = ">";
1580 break;
1581
1582 default:
1583 return 0;
1584 }
1585
1587
1588 /*
1589 * Split the output into lines for readability; sometimes multiple headers
1590 * are included in a single call. We also don't allow unprintable ASCII
1591 * through without a basic <XX> escape.
1592 */
1593 for (int i = 0; i < size; i++)
1594 {
1595 char c = data[i];
1596
1597 if (!printed_prefix)
1598 {
1599 appendPQExpBuffer(&buf, "[libcurl] %s ", prefix);
1600 printed_prefix = true;
1601 }
1602
1603 if (c >= 0x20 && c <= 0x7E)
1605 else if ((type == CURLINFO_HEADER_IN
1606 || type == CURLINFO_HEADER_OUT
1607 || type == CURLINFO_TEXT)
1608 && (c == '\r' || c == '\n'))
1609 {
1610 /*
1611 * Don't bother emitting <0D><0A> for headers and text; it's not
1612 * helpful noise.
1613 */
1614 }
1615 else
1616 appendPQExpBuffer(&buf, "<%02X>", c);
1617
1618 if (c == '\n')
1619 {
1621 printed_prefix = false;
1622 }
1623 }
1624
1625 if (printed_prefix)
1626 appendPQExpBufferChar(&buf, '\n'); /* finish the line */
1627
1628 fprintf(stderr, "%s", buf.data);
1630 return 0;
1631}
#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 1863 of file oauth-curl.c.

1864{
1865 CURLMcode err;
1866 CURLMsg *msg;
1867 int msgs_left;
1868 bool done;
1869
1870 if (actx->running)
1871 {
1872 /*---
1873 * There's an async request in progress. Pump the multi handle.
1874 *
1875 * curl_multi_socket_all() is officially deprecated, because it's
1876 * inefficient and pointless if your event loop has already handed you
1877 * the exact sockets that are ready. But that's not our use case --
1878 * our client has no way to tell us which sockets are ready. (They
1879 * don't even know there are sockets to begin with.)
1880 *
1881 * We can grab the list of triggered events from the multiplexer
1882 * ourselves, but that's effectively what curl_multi_socket_all() is
1883 * going to do. And there are currently no plans for the Curl project
1884 * to remove or break this API, so ignore the deprecation. See
1885 *
1886 * https://curl.se/mail/lib-2024-11/0028.html
1887 *
1888 */
1890 err = curl_multi_socket_all(actx->curlm, &actx->running);
1891 )
1892
1893 if (err)
1894 {
1895 actx_error(actx, "asynchronous HTTP request failed: %s",
1896 curl_multi_strerror(err));
1897 return PGRES_POLLING_FAILED;
1898 }
1899
1900 if (actx->running)
1901 {
1902 /* We'll come back again. */
1903 return PGRES_POLLING_READING;
1904 }
1905 }
1906
1907 done = false;
1908 while ((msg = curl_multi_info_read(actx->curlm, &msgs_left)) != NULL)
1909 {
1910 if (msg->msg != CURLMSG_DONE)
1911 {
1912 /*
1913 * Future libcurl versions may define new message types; we don't
1914 * know how to handle them, so we'll ignore them.
1915 */
1916 continue;
1917 }
1918
1919 /* First check the status of the request itself. */
1920 if (msg->data.result != CURLE_OK)
1921 {
1922 /*
1923 * If a more specific error hasn't already been reported, use
1924 * libcurl's description.
1925 */
1926 if (actx->errbuf.len == 0)
1927 actx_error_str(actx, curl_easy_strerror(msg->data.result));
1928
1929 return PGRES_POLLING_FAILED;
1930 }
1931
1932 /* Now remove the finished handle; we'll add it back later if needed. */
1933 err = curl_multi_remove_handle(actx->curlm, msg->easy_handle);
1934 if (err)
1935 {
1936 actx_error(actx, "libcurl easy handle removal failed: %s",
1937 curl_multi_strerror(err));
1938 return PGRES_POLLING_FAILED;
1939 }
1940
1941 done = true;
1942 }
1943
1944 /* Sanity check. */
1945 if (!done)
1946 {
1947 actx_error(actx, "no result was retrieved for the finished handle");
1948 return PGRES_POLLING_FAILED;
1949 }
1950
1951 return PGRES_POLLING_OK;
1952}
void err(int eval, const char *fmt,...)
Definition: err.c:43
if(TABLE==NULL||TABLE_index==NULL)
Definition: isn.c:81
@ PGRES_POLLING_OK
Definition: libpq-fe.h:118
@ PGRES_POLLING_READING
Definition: libpq-fe.h:116
@ PGRES_POLLING_FAILED
Definition: libpq-fe.h:115
#define actx_error_str(ACTX, S)
Definition: oauth-curl.c:376
#define CURL_IGNORE_DEPRECATION(x)
Definition: oauth-curl.c:1855
int running
Definition: oauth-curl.c:277
PQExpBufferData errbuf
Definition: oauth-curl.c:267
CURLM * curlm
Definition: oauth-curl.c:236

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 2343 of file oauth-curl.c.

2344{
2345 long response_code;
2346
2347 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2348
2349 /*
2350 * Per RFC 8628, Section 3, a successful device authorization response
2351 * uses 200 OK.
2352 */
2353 if (response_code == 200)
2354 {
2355 actx->errctx = "failed to parse device authorization";
2356 if (!parse_device_authz(actx, &actx->authz))
2357 return false; /* error message already set */
2358
2359 return true;
2360 }
2361
2362 /*
2363 * The device authorization endpoint uses the same error response as the
2364 * token endpoint, so the error handling roughly follows
2365 * finish_token_request(). The key difference is that an error here is
2366 * immediately fatal.
2367 */
2368 if (response_code == 400 || response_code == 401)
2369 {
2370 struct token_error err = {0};
2371
2372 if (!parse_token_error(actx, &err))
2373 {
2375 return false;
2376 }
2377
2378 /* Copy the token error into the context error buffer */
2379 record_token_error(actx, &err);
2380
2382 return false;
2383 }
2384
2385 /* Any other response codes are considered invalid */
2386 actx_error(actx, "unexpected response code %ld", response_code);
2387 return false;
2388}
static bool parse_token_error(struct async_ctx *actx, struct token_error *err)
Definition: oauth-curl.c:1071
static void record_token_error(struct async_ctx *actx, const struct token_error *err)
Definition: oauth-curl.c:1099
static bool parse_device_authz(struct async_ctx *actx, struct device_authz *authz)
Definition: oauth-curl.c:1017
static void free_token_error(struct token_error *err)
Definition: oauth-curl.c:176
struct device_authz authz
Definition: oauth-curl.c:275
const char * errctx
Definition: oauth-curl.c:266

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 2059 of file oauth-curl.c.

2060{
2061 long response_code;
2062
2063 /*----
2064 * Now check the response. OIDC Discovery 1.0 is pretty strict:
2065 *
2066 * A successful response MUST use the 200 OK HTTP status code and
2067 * return a JSON object using the application/json content type that
2068 * contains a set of Claims as its members that are a subset of the
2069 * Metadata values defined in Section 3.
2070 *
2071 * Compared to standard HTTP semantics, this makes life easy -- we don't
2072 * need to worry about redirections (which would call the Issuer host
2073 * validation into question), or non-authoritative responses, or any other
2074 * complications.
2075 */
2076 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2077
2078 if (response_code != 200)
2079 {
2080 actx_error(actx, "unexpected response code %ld", response_code);
2081 return false;
2082 }
2083
2084 /*
2085 * Pull the fields we care about from the document.
2086 */
2087 actx->errctx = "failed to parse OpenID discovery document";
2088 if (!parse_provider(actx, &actx->provider))
2089 return false; /* error message already set */
2090
2091 /*
2092 * Fill in any defaults for OPTIONAL/RECOMMENDED fields we care about.
2093 */
2095 {
2096 /*
2097 * Per Section 3, the default is ["authorization_code", "implicit"].
2098 */
2099 struct curl_slist *temp = actx->provider.grant_types_supported;
2100
2101 temp = curl_slist_append(temp, "authorization_code");
2102 if (temp)
2103 {
2104 temp = curl_slist_append(temp, "implicit");
2105 }
2106
2107 if (!temp)
2108 {
2109 actx_error(actx, "out of memory");
2110 return false;
2111 }
2112
2113 actx->provider.grant_types_supported = temp;
2114 }
2115
2116 return true;
2117}
static bool parse_provider(struct async_ctx *actx, struct provider *provider)
Definition: oauth-curl.c:906
struct curl_slist * grant_types_supported
Definition: oauth-curl.c:119

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 2432 of file oauth-curl.c.

2433{
2434 long response_code;
2435
2436 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2437
2438 /*
2439 * Per RFC 6749, Section 5, a successful response uses 200 OK.
2440 */
2441 if (response_code == 200)
2442 {
2443 actx->errctx = "failed to parse access token response";
2444 if (!parse_access_token(actx, tok))
2445 return false; /* error message already set */
2446
2447 return true;
2448 }
2449
2450 /*
2451 * An error response uses either 400 Bad Request or 401 Unauthorized.
2452 * There are references online to implementations using 403 for error
2453 * return which would violate the specification. For now we stick to the
2454 * specification but we might have to revisit this.
2455 */
2456 if (response_code == 400 || response_code == 401)
2457 {
2458 if (!parse_token_error(actx, &tok->err))
2459 return false;
2460
2461 return true;
2462 }
2463
2464 /* Any other response codes are considered invalid */
2465 actx_error(actx, "unexpected response code %ld", response_code);
2466 return false;
2467}
static bool parse_access_token(struct async_ctx *actx, struct token *tok)
Definition: oauth-curl.c:1131
struct token_error err
Definition: oauth-curl.c:198

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 287 of file oauth-curl.c.

288{
289 /*
290 * In general, none of the error cases below should ever happen if we have
291 * no bugs above. But if we do hit them, surfacing those errors somehow
292 * might be the only way to have a chance to debug them.
293 *
294 * TODO: At some point it'd be nice to have a standard way to warn about
295 * teardown failures. Appending to the connection's error message only
296 * helps if the bug caused a connection failure; otherwise it'll be
297 * buried...
298 */
299
300 if (actx->curlm && actx->curl)
301 {
302 CURLMcode err = curl_multi_remove_handle(actx->curlm, actx->curl);
303
304 if (err)
306 "libcurl easy handle removal failed: %s",
307 curl_multi_strerror(err));
308 }
309
310 if (actx->curl)
311 {
312 /*
313 * curl_multi_cleanup() doesn't free any associated easy handles; we
314 * need to do that separately. We only ever have one easy handle per
315 * multi handle.
316 */
317 curl_easy_cleanup(actx->curl);
318 }
319
320 if (actx->curlm)
321 {
322 CURLMcode err = curl_multi_cleanup(actx->curlm);
323
324 if (err)
326 "libcurl multi handle cleanup failed: %s",
327 curl_multi_strerror(err));
328 }
329
330 free_provider(&actx->provider);
331 free_device_authz(&actx->authz);
332
333 curl_slist_free_all(actx->headers);
335 termPQExpBuffer(&actx->errbuf);
336
337 if (actx->mux != PGINVALID_SOCKET)
338 close(actx->mux);
339 if (actx->timerfd >= 0)
340 close(actx->timerfd);
341
342 free(actx);
343}
#define close(a)
Definition: win32.h:12
static void free_provider(struct provider *provider)
Definition: oauth-curl.c:123
static void free_device_authz(struct device_authz *authz)
Definition: oauth-curl.c:151
void libpq_append_conn_error(PGconn *conn, const char *fmt,...)
Definition: oauth-utils.c:95
#define PGINVALID_SOCKET
Definition: port.h:31
CURL * curl
Definition: oauth-curl.c:238
pgsocket mux
Definition: oauth-curl.c:233
int timerfd
Definition: oauth-curl.c:232
struct curl_slist * headers
Definition: oauth-curl.c:241

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 202 of file oauth-curl.c.

203{
204 free(tok->access_token);
205 free(tok->token_type);
206 free_token_error(&tok->err);
207}
char * token_type
Definition: oauth-curl.c:195
char * access_token
Definition: oauth-curl.c:194

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 176 of file oauth-curl.c.

177{
178 free(err->error);
179 free(err->error_description);
180}

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 2479 of file oauth-curl.c.

2480{
2481 bool success = false;
2482 struct token tok = {0};
2483 const struct token_error *err;
2484
2485 if (!finish_token_request(actx, &tok))
2486 goto token_cleanup;
2487
2488 /* A successful token request gives either a token or an in-band error. */
2489 Assert(tok.access_token || tok.err.error);
2490
2491 if (tok.access_token)
2492 {
2493 *token = tok.access_token;
2494 tok.access_token = NULL;
2495
2496 success = true;
2497 goto token_cleanup;
2498 }
2499
2500 /*
2501 * authorization_pending and slow_down are the only acceptable errors;
2502 * anything else and we bail. These are defined in RFC 8628, Sec. 3.5.
2503 */
2504 err = &tok.err;
2505 if (strcmp(err->error, "authorization_pending") != 0 &&
2506 strcmp(err->error, "slow_down") != 0)
2507 {
2508 record_token_error(actx, err);
2509 goto token_cleanup;
2510 }
2511
2512 /*
2513 * A slow_down error requires us to permanently increase our retry
2514 * interval by five seconds.
2515 */
2516 if (strcmp(err->error, "slow_down") == 0)
2517 {
2518 int prev_interval = actx->authz.interval;
2519
2520 actx->authz.interval += 5;
2521 if (actx->authz.interval < prev_interval)
2522 {
2523 actx_error(actx, "slow_down interval overflow");
2524 goto token_cleanup;
2525 }
2526 }
2527
2528 success = true;
2529
2530token_cleanup:
2531 free_token(&tok);
2532 return success;
2533}
static bool finish_token_request(struct async_ctx *actx, struct token *tok)
Definition: oauth-curl.c:2432
static void free_token(struct token *tok)
Definition: oauth-curl.c:202
char * error
Definition: oauth-curl.c:171

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 2585 of file oauth-curl.c.

2586{
2587 /*
2588 * Don't let the compiler play tricks with this variable. In the
2589 * HAVE_THREADSAFE_CURL_GLOBAL_INIT case, we don't care if two threads
2590 * enter simultaneously, but we do care if this gets set transiently to
2591 * PG_BOOL_YES/NO in cases where that's not the final answer.
2592 */
2593 static volatile PGTernaryBool init_successful = PG_BOOL_UNKNOWN;
2594#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
2595 curl_version_info_data *info;
2596#endif
2597
2598#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2599
2600 /*
2601 * Lock around the whole function. If a libpq client performs its own work
2602 * with libcurl, it must either ensure that Curl is initialized safely
2603 * before calling us (in which case our call will be a no-op), or else it
2604 * must guard its own calls to curl_global_init() with a registered
2605 * threadlock handler. See PQregisterThreadLock().
2606 */
2607 pglock_thread();
2608#endif
2609
2610 /*
2611 * Skip initialization if we've already done it. (Curl tracks the number
2612 * of calls; there's no point in incrementing the counter every time we
2613 * connect.)
2614 */
2615 if (init_successful == PG_BOOL_YES)
2616 goto done;
2617 else if (init_successful == PG_BOOL_NO)
2618 {
2620 "curl_global_init previously failed during OAuth setup");
2621 goto done;
2622 }
2623
2624 /*
2625 * We know we've already initialized Winsock by this point (see
2626 * pqMakeEmptyPGconn()), so we should be able to safely skip that bit. But
2627 * we have to tell libcurl to initialize everything else, because other
2628 * pieces of our client executable may already be using libcurl for their
2629 * own purposes. If we initialize libcurl with only a subset of its
2630 * features, we could break those other clients nondeterministically, and
2631 * that would probably be a nightmare to debug.
2632 *
2633 * If some other part of the program has already called this, it's a
2634 * no-op.
2635 */
2636 if (curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32) != CURLE_OK)
2637 {
2639 "curl_global_init failed during OAuth setup");
2640 init_successful = PG_BOOL_NO;
2641 goto done;
2642 }
2643
2644#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
2645
2646 /*
2647 * If we determined at configure time that the Curl installation is
2648 * thread-safe, our job here is much easier. We simply initialize above
2649 * without any locking (concurrent or duplicated calls are fine in that
2650 * situation), then double-check to make sure the runtime setting agrees,
2651 * to try to catch silent downgrades.
2652 */
2653 info = curl_version_info(CURLVERSION_NOW);
2654 if (!(info->features & CURL_VERSION_THREADSAFE))
2655 {
2656 /*
2657 * In a downgrade situation, the damage is already done. Curl global
2658 * state may be corrupted. Be noisy.
2659 */
2660 libpq_append_conn_error(conn, "libcurl is no longer thread-safe\n"
2661 "\tCurl initialization was reported thread-safe when libpq\n"
2662 "\twas compiled, but the currently installed version of\n"
2663 "\tlibcurl reports that it is not. Recompile libpq against\n"
2664 "\tthe installed version of libcurl.");
2665 init_successful = PG_BOOL_NO;
2666 goto done;
2667 }
2668#endif
2669
2670 init_successful = PG_BOOL_YES;
2671
2672done:
2673#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2675#endif
2676 return (init_successful == PG_BOOL_YES);
2677}
PGTernaryBool
Definition: oauth-utils.h:72
@ PG_BOOL_YES
Definition: oauth-utils.h:74
@ PG_BOOL_NO
Definition: oauth-utils.h:75
@ PG_BOOL_UNKNOWN
Definition: oauth-utils.h:73
#define pglock_thread()
Definition: oauth-utils.h:91
#define pgunlock_thread()
Definition: oauth-utils.h:92

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 632 of file oauth-curl.c.

633{
634 struct oauth_parse *ctx = state;
635
636 if (ctx->active)
637 {
638 /*
639 * Clear the target (which should be an array inside the top-level
640 * object). For this to be safe, no target arrays can contain other
641 * arrays; we check for that in the array_start callback.
642 */
643 if (ctx->nested != 2 || ctx->active->type != JSON_TOKEN_ARRAY_START)
644 {
645 Assert(false);
647 "internal error: found unexpected array end while parsing field '%s'",
648 ctx->active->name);
650 }
651
652 ctx->active = NULL;
653 }
654
655 --ctx->nested;
656 return JSON_SUCCESS;
657}
@ JSON_SEM_ACTION_FAILED
Definition: jsonapi.h:59
@ JSON_SUCCESS
Definition: jsonapi.h:36
@ JSON_TOKEN_ARRAY_START
Definition: jsonapi.h:24
#define oauth_parse_set_error(ctx, fmt,...)
Definition: oauth-curl.c:460
const char * name
Definition: oauth-curl.c:432
JsonTokenType type
Definition: oauth-curl.c:434
const struct json_field * active
Definition: oauth-curl.c:457
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 600 of file oauth-curl.c.

601{
602 struct oauth_parse *ctx = state;
603
604 if (!ctx->nested)
605 {
606 oauth_parse_set_error(ctx, "top-level element must be an object");
608 }
609
610 if (ctx->active)
611 {
613 /* The arrays we care about must not have arrays as values. */
614 || ctx->nested > 1)
615 {
618 }
619 }
620
621 ++ctx->nested;
623 {
624 oauth_parse_set_error(ctx, "JSON is too deeply nested");
626 }
627
628 return JSON_SUCCESS;
629}
static void report_type_mismatch(struct oauth_parse *ctx)
Definition: oauth-curl.c:464
#define MAX_OAUTH_NESTING_LEVEL
Definition: oauth-curl.c:97

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

Referenced by parse_oauth_json().

◆ oauth_json_object_end()

static JsonParseErrorType oauth_json_object_end ( void *  state)
static

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

578{
579 struct oauth_parse *ctx = state;
580
581 --ctx->nested;
582
583 /*
584 * All fields should be fully processed by the end of the top-level
585 * object.
586 */
587 if (!ctx->nested && ctx->active)
588 {
589 Assert(false);
591 "internal error: field '%s' still active at end of object",
592 ctx->active->name);
594 }
595
596 return JSON_SUCCESS;
597}

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 522 of file oauth-curl.c.

523{
524 struct oauth_parse *ctx = state;
525
526 /* We care only about the top-level fields. */
527 if (ctx->nested == 1)
528 {
529 const struct json_field *field = ctx->fields;
530
531 /*
532 * We should never start parsing a new field while a previous one is
533 * still active.
534 */
535 if (ctx->active)
536 {
537 Assert(false);
539 "internal error: started field '%s' before field '%s' was finished",
540 name, ctx->active->name);
542 }
543
544 while (field->name)
545 {
546 if (strcmp(name, field->name) == 0)
547 {
548 ctx->active = field;
549 break;
550 }
551
552 ++field;
553 }
554
555 /*
556 * We don't allow duplicate field names; error out if the target has
557 * already been set.
558 */
559 if (ctx->active)
560 {
561 field = ctx->active;
562
563 if ((field->type == JSON_TOKEN_ARRAY_START && *field->target.array)
564 || (field->type != JSON_TOKEN_ARRAY_START && *field->target.scalar))
565 {
566 oauth_parse_set_error(ctx, "field \"%s\" is duplicated",
567 field->name);
569 }
570 }
571 }
572
573 return JSON_SUCCESS;
574}
struct curl_slist ** array
Definition: oauth-curl.c:440
union json_field::@188 target
char ** scalar
Definition: oauth-curl.c:439
const struct json_field * fields
Definition: oauth-curl.c:456
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 497 of file oauth-curl.c.

498{
499 struct oauth_parse *ctx = state;
500
501 if (ctx->active)
502 {
503 /*
504 * Currently, none of the fields we're interested in can be or contain
505 * objects, so we can reject this case outright.
506 */
509 }
510
511 ++ctx->nested;
513 {
514 oauth_parse_set_error(ctx, "JSON is too deeply nested");
516 }
517
518 return JSON_SUCCESS;
519}

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

Referenced by parse_oauth_json().

◆ oauth_json_scalar()

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

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

661{
662 struct oauth_parse *ctx = state;
663
664 if (!ctx->nested)
665 {
666 oauth_parse_set_error(ctx, "top-level element must be an object");
668 }
669
670 if (ctx->active)
671 {
672 const struct json_field *field = ctx->active;
673 JsonTokenType expected = field->type;
674
675 /* Make sure this matches what the active field expects. */
676 if (expected == JSON_TOKEN_ARRAY_START)
677 {
678 /* Are we actually inside an array? */
679 if (ctx->nested < 2)
680 {
683 }
684
685 /* Currently, arrays can only contain strings. */
686 expected = JSON_TOKEN_STRING;
687 }
688
689 if (type != expected)
690 {
693 }
694
695 if (field->type != JSON_TOKEN_ARRAY_START)
696 {
697 /* Ensure that we're parsing the top-level keys... */
698 if (ctx->nested != 1)
699 {
700 Assert(false);
702 "internal error: scalar target found at nesting level %d",
703 ctx->nested);
705 }
706
707 /* ...and that a result has not already been set. */
708 if (*field->target.scalar)
709 {
710 Assert(false);
712 "internal error: scalar field '%s' would be assigned twice",
713 ctx->active->name);
715 }
716
717 *field->target.scalar = strdup(token);
718 if (!*field->target.scalar)
719 return JSON_OUT_OF_MEMORY;
720
721 ctx->active = NULL;
722
723 return JSON_SUCCESS;
724 }
725 else
726 {
727 struct curl_slist *temp;
728
729 /* The target array should be inside the top-level object. */
730 if (ctx->nested != 2)
731 {
732 Assert(false);
734 "internal error: array member found at nesting level %d",
735 ctx->nested);
737 }
738
739 /* Note that curl_slist_append() makes a copy of the token. */
740 temp = curl_slist_append(*field->target.array, token);
741 if (!temp)
742 return JSON_OUT_OF_MEMORY;
743
744 *field->target.array = temp;
745 }
746 }
747 else
748 {
749 /* otherwise we just ignore it */
750 }
751
752 return JSON_SUCCESS;
753}
@ 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 1131 of file oauth-curl.c.

1132{
1133 struct json_field fields[] = {
1134 {"access_token", JSON_TOKEN_STRING, {&tok->access_token}, PG_OAUTH_REQUIRED},
1135 {"token_type", JSON_TOKEN_STRING, {&tok->token_type}, PG_OAUTH_REQUIRED},
1136
1137 /*---
1138 * We currently have no use for the following OPTIONAL fields:
1139 *
1140 * - expires_in: This will be important for maintaining a token cache,
1141 * but we do not yet implement one.
1142 *
1143 * - refresh_token: Ditto.
1144 *
1145 * - scope: This is only sent when the authorization server sees fit to
1146 * change our scope request. It's not clear what we should do
1147 * about this; either it's been done as a matter of policy, or
1148 * the user has explicitly denied part of the authorization,
1149 * and either way the server-side validator is in a better
1150 * place to complain if the change isn't acceptable.
1151 */
1152
1153 {0},
1154 };
1155
1156 return parse_oauth_json(actx, fields);
1157}
static bool parse_oauth_json(struct async_ctx *actx, const struct json_field *fields)
Definition: oauth-curl.c:819
#define PG_OAUTH_REQUIRED
Definition: oauth-curl.c:447

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 1017 of file oauth-curl.c.

1018{
1019 struct json_field fields[] = {
1020 {"device_code", JSON_TOKEN_STRING, {&authz->device_code}, PG_OAUTH_REQUIRED},
1021 {"user_code", JSON_TOKEN_STRING, {&authz->user_code}, PG_OAUTH_REQUIRED},
1022 {"verification_uri", JSON_TOKEN_STRING, {&authz->verification_uri}, PG_OAUTH_REQUIRED},
1023 {"expires_in", JSON_TOKEN_NUMBER, {&authz->expires_in_str}, PG_OAUTH_REQUIRED},
1024
1025 /*
1026 * Some services (Google, Azure) spell verification_uri differently.
1027 * We accept either.
1028 */
1029 {"verification_url", JSON_TOKEN_STRING, {&authz->verification_uri}, PG_OAUTH_REQUIRED},
1030
1031 /*
1032 * There is no evidence of verification_uri_complete being spelled
1033 * with "url" instead with any service provider, so only support
1034 * "uri".
1035 */
1036 {"verification_uri_complete", JSON_TOKEN_STRING, {&authz->verification_uri_complete}, PG_OAUTH_OPTIONAL},
1037 {"interval", JSON_TOKEN_NUMBER, {&authz->interval_str}, PG_OAUTH_OPTIONAL},
1038
1039 {0},
1040 };
1041
1042 if (!parse_oauth_json(actx, fields))
1043 return false;
1044
1045 /*
1046 * Parse our numeric fields. Lexing has already completed by this time, so
1047 * we at least know they're valid JSON numbers.
1048 */
1049 if (authz->interval_str)
1050 authz->interval = parse_interval(actx, authz->interval_str);
1051 else
1052 {
1053 /*
1054 * RFC 8628 specifies 5 seconds as the default value if the server
1055 * doesn't provide an interval.
1056 */
1057 authz->interval = 5;
1058 }
1059
1060 Assert(authz->expires_in_str); /* ensured by parse_oauth_json() */
1061 authz->expires_in = parse_expires_in(actx, authz->expires_in_str);
1062
1063 return true;
1064}
@ JSON_TOKEN_NUMBER
Definition: jsonapi.h:21
static int parse_interval(struct async_ctx *actx, const char *interval_str)
Definition: oauth-curl.c:972
#define PG_OAUTH_OPTIONAL
Definition: oauth-curl.c:448
static int parse_expires_in(struct async_ctx *actx, const char *expires_in_str)
Definition: oauth-curl.c:998

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 998 of file oauth-curl.c.

999{
1000 double parsed;
1001
1002 parsed = parse_json_number(expires_in_str);
1003 parsed = floor(parsed);
1004
1005 if (parsed >= INT_MAX)
1006 return INT_MAX;
1007 else if (parsed <= INT_MIN)
1008 return INT_MIN;
1009
1010 return parsed;
1011}
static double parse_json_number(const char *s)
Definition: oauth-curl.c:937

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 972 of file oauth-curl.c.

973{
974 double parsed;
975
976 parsed = parse_json_number(interval_str);
977 parsed = ceil(parsed);
978
979 if (parsed < 1)
980 return actx->debugging ? 0 : 1;
981
982 else if (parsed >= INT_MAX)
983 return INT_MAX;
984
985 return parsed;
986}

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 937 of file oauth-curl.c.

938{
939 double parsed;
940 int cnt;
941
942 /*
943 * The JSON lexer has already validated the number, which is stricter than
944 * the %f format, so we should be good to use sscanf().
945 */
946 cnt = sscanf(s, "%lf", &parsed);
947
948 if (cnt != 1)
949 {
950 /*
951 * Either the lexer screwed up or our assumption above isn't true, and
952 * either way a developer needs to take a look.
953 */
954 Assert(false);
955 return 0;
956 }
957
958 return parsed;
959}

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 819 of file oauth-curl.c.

820{
821 PQExpBuffer resp = &actx->work_data;
822 JsonLexContext lex = {0};
823 JsonSemAction sem = {0};
825 struct oauth_parse ctx = {0};
826 bool success = false;
827
828 if (!check_content_type(actx, "application/json"))
829 return false;
830
831 if (strlen(resp->data) != resp->len)
832 {
833 actx_error(actx, "response contains embedded NULLs");
834 return false;
835 }
836
837 /*
838 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
839 * that up front.
840 */
841 if (pg_encoding_verifymbstr(PG_UTF8, resp->data, resp->len) != resp->len)
842 {
843 actx_error(actx, "response is not valid UTF-8");
844 return false;
845 }
846
847 makeJsonLexContextCstringLen(&lex, resp->data, resp->len, PG_UTF8, true);
848 setJsonLexContextOwnsTokens(&lex, true); /* must not leak on error */
849
850 ctx.errbuf = &actx->errbuf;
851 ctx.fields = fields;
852 sem.semstate = &ctx;
853
860
861 err = pg_parse_json(&lex, &sem);
862
863 if (err != JSON_SUCCESS)
864 {
865 /*
866 * For JSON_SEM_ACTION_FAILED, we've already written the error
867 * message. Other errors come directly from pg_parse_json(), already
868 * translated.
869 */
871 actx_error_str(actx, json_errdetail(err, &lex));
872
873 goto cleanup;
874 }
875
876 /* Check all required fields. */
877 while (fields->name)
878 {
879 if (fields->required
880 && !*fields->target.scalar
881 && !*fields->target.array)
882 {
883 actx_error(actx, "field \"%s\" is missing", fields->name);
884 goto cleanup;
885 }
886
887 fields++;
888 }
889
890 success = true;
891
892cleanup:
893 freeJsonLexContext(&lex);
894 return success;
895}
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:2404
void freeJsonLexContext(JsonLexContext *lex)
Definition: jsonapi.c:687
JsonParseErrorType
Definition: jsonapi.h:35
static JsonParseErrorType oauth_json_array_end(void *state)
Definition: oauth-curl.c:632
static JsonParseErrorType oauth_json_object_field_start(void *state, char *name, bool isnull)
Definition: oauth-curl.c:522
static JsonParseErrorType oauth_json_scalar(void *state, char *token, JsonTokenType type)
Definition: oauth-curl.c:660
static JsonParseErrorType oauth_json_array_start(void *state)
Definition: oauth-curl.c:600
static JsonParseErrorType oauth_json_object_end(void *state)
Definition: oauth-curl.c:577
static bool check_content_type(struct async_ctx *actx, const char *type)
Definition: oauth-curl.c:760
static JsonParseErrorType oauth_json_object_start(void *state)
Definition: oauth-curl.c:497
@ 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
bool required
Definition: oauth-curl.c:443
PQExpBuffer errbuf
Definition: oauth-curl.c:453
static JsonSemAction sem
int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len)
Definition: wchar.c:2202

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 906 of file oauth-curl.c.

907{
908 struct json_field fields[] = {
911
912 /*----
913 * The following fields are technically REQUIRED, but we don't use
914 * them anywhere yet:
915 *
916 * - jwks_uri
917 * - response_types_supported
918 * - subject_types_supported
919 * - id_token_signing_alg_values_supported
920 */
921
922 {"device_authorization_endpoint", JSON_TOKEN_STRING, {&provider->device_authorization_endpoint}, PG_OAUTH_OPTIONAL},
923 {"grant_types_supported", JSON_TOKEN_ARRAY_START, {.array = &provider->grant_types_supported}, PG_OAUTH_OPTIONAL},
924
925 {0},
926 };
927
928 return parse_oauth_json(actx, fields);
929}

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 1071 of file oauth-curl.c.

1072{
1073 bool result;
1074 struct json_field fields[] = {
1075 {"error", JSON_TOKEN_STRING, {&err->error}, PG_OAUTH_REQUIRED},
1076
1077 {"error_description", JSON_TOKEN_STRING, {&err->error_description}, PG_OAUTH_OPTIONAL},
1078
1079 {0},
1080 };
1081
1082 result = parse_oauth_json(actx, fields);
1083
1084 /*
1085 * Since token errors are parsed during other active error paths, only
1086 * override the errctx if parsing explicitly fails.
1087 */
1088 if (!result)
1089 actx->errctx = "failed to parse token error response";
1090
1091 return result;
1092}

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 354 of file oauth-curl.c.

355{
357
358 if (state->async_ctx)
359 {
360 free_async_ctx(conn, state->async_ctx);
361 state->async_ctx = NULL;
362 }
363
365}
#define set_conn_altsock(CONN, VAL)
Definition: oauth-curl.c:61
static void free_async_ctx(PGconn *conn, struct async_ctx *actx)
Definition: oauth-curl.c:287
#define conn_sasl_state(CONN)
Definition: oauth-curl.c:59

References conn, conn_sasl_state, free_async_ctx(), PGINVALID_SOCKET, and set_conn_altsock.

◆ pg_fe_run_oauth_flow()

PostgresPollingStatusType pg_fe_run_oauth_flow ( PGconn conn)

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

2933{
2935#ifndef WIN32
2936 sigset_t osigset;
2937 bool sigpipe_pending;
2938 bool masked;
2939
2940 /*---
2941 * Ignore SIGPIPE on this thread during all Curl processing.
2942 *
2943 * Because we support multiple threads, we have to set up libcurl with
2944 * CURLOPT_NOSIGNAL, which disables its default global handling of
2945 * SIGPIPE. From the Curl docs:
2946 *
2947 * libcurl makes an effort to never cause such SIGPIPE signals to
2948 * trigger, but some operating systems have no way to avoid them and
2949 * even on those that have there are some corner cases when they may
2950 * still happen, contrary to our desire.
2951 *
2952 * Note that libcurl is also at the mercy of its DNS resolution and SSL
2953 * libraries; if any of them forget a MSG_NOSIGNAL then we're in trouble.
2954 * Modern platforms and libraries seem to get it right, so this is a
2955 * difficult corner case to exercise in practice, and unfortunately it's
2956 * not really clear whether it's necessary in all cases.
2957 */
2958 masked = (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0);
2959#endif
2960
2962
2963#ifndef WIN32
2964 if (masked)
2965 {
2966 /*
2967 * Undo the SIGPIPE mask. Assume we may have gotten EPIPE (we have no
2968 * way of knowing at this level).
2969 */
2970 pq_reset_sigpipe(&osigset, sigpipe_pending, true /* EPIPE, maybe */ );
2971 }
2972#endif
2973
2974 return result;
2975}
PostgresPollingStatusType
Definition: libpq-fe.h:114
static PostgresPollingStatusType pg_fe_run_oauth_flow_impl(PGconn *conn)
Definition: oauth-curl.c:2694
void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
Definition: oauth-utils.c:208
int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
Definition: oauth-utils.c:172

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

◆ pg_fe_run_oauth_flow_impl()

static PostgresPollingStatusType pg_fe_run_oauth_flow_impl ( PGconn conn)
static

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

2695{
2697 struct async_ctx *actx;
2698 char *oauth_token = NULL;
2700
2701 if (!initialize_curl(conn))
2702 return PGRES_POLLING_FAILED;
2703
2704 if (!state->async_ctx)
2705 {
2706 /*
2707 * Create our asynchronous state, and hook it into the upper-level
2708 * OAuth state immediately, so any failures below won't leak the
2709 * context allocation.
2710 */
2711 actx = calloc(1, sizeof(*actx));
2712 if (!actx)
2713 {
2714 libpq_append_conn_error(conn, "out of memory");
2715 return PGRES_POLLING_FAILED;
2716 }
2717
2718 actx->mux = PGINVALID_SOCKET;
2719 actx->timerfd = -1;
2720
2721 /* Should we enable unsafe features? */
2723
2724 state->async_ctx = actx;
2725
2726 initPQExpBuffer(&actx->work_data);
2727 initPQExpBuffer(&actx->errbuf);
2728
2729 if (!setup_multiplexer(actx))
2730 goto error_return;
2731
2732 if (!setup_curl_handles(actx))
2733 goto error_return;
2734 }
2735
2736 actx = state->async_ctx;
2737
2738 do
2739 {
2740 /* By default, the multiplexer is the altsock. Reassign as desired. */
2741 set_conn_altsock(conn, actx->mux);
2742
2743 switch (actx->step)
2744 {
2745 case OAUTH_STEP_INIT:
2746 break;
2747
2751 {
2753
2754 status = drive_request(actx);
2755
2756 if (status == PGRES_POLLING_FAILED)
2757 goto error_return;
2758 else if (status != PGRES_POLLING_OK)
2759 {
2760 /* not done yet */
2761 return status;
2762 }
2763
2764 break;
2765 }
2766
2768
2769 /*
2770 * The client application is supposed to wait until our timer
2771 * expires before calling PQconnectPoll() again, but that
2772 * might not happen. To avoid sending a token request early,
2773 * check the timer before continuing.
2774 */
2775 if (!timer_expired(actx))
2776 {
2778 return PGRES_POLLING_READING;
2779 }
2780
2781 /* Disable the expired timer. */
2782 if (!set_timer(actx, -1))
2783 goto error_return;
2784
2785 break;
2786 }
2787
2788 /*
2789 * Each case here must ensure that actx->running is set while we're
2790 * waiting on some asynchronous work. Most cases rely on
2791 * start_request() to do that for them.
2792 */
2793 switch (actx->step)
2794 {
2795 case OAUTH_STEP_INIT:
2796 actx->errctx = "failed to fetch OpenID discovery document";
2798 goto error_return;
2799
2800 actx->step = OAUTH_STEP_DISCOVERY;
2801 break;
2802
2804 if (!finish_discovery(actx))
2805 goto error_return;
2806
2807 if (!check_issuer(actx, conn))
2808 goto error_return;
2809
2810 actx->errctx = "cannot run OAuth device authorization";
2811 if (!check_for_device_flow(actx))
2812 goto error_return;
2813
2814 actx->errctx = "failed to obtain device authorization";
2815 if (!start_device_authz(actx, conn))
2816 goto error_return;
2817
2819 break;
2820
2822 if (!finish_device_authz(actx))
2823 goto error_return;
2824
2825 actx->errctx = "failed to obtain access token";
2826 if (!start_token_request(actx, conn))
2827 goto error_return;
2828
2830 break;
2831
2833 if (!handle_token_response(actx, &oauth_token))
2834 goto error_return;
2835
2836 /*
2837 * Hook any oauth_token into the PGconn immediately so that
2838 * the allocation isn't lost in case of an error.
2839 */
2840 set_conn_oauth_token(conn, oauth_token);
2841
2842 if (!actx->user_prompted)
2843 {
2844 /*
2845 * Now that we know the token endpoint isn't broken, give
2846 * the user the login instructions.
2847 */
2848 if (!prompt_user(actx, conn))
2849 goto error_return;
2850
2851 actx->user_prompted = true;
2852 }
2853
2854 if (oauth_token)
2855 break; /* done! */
2856
2857 /*
2858 * Wait for the required interval before issuing the next
2859 * request.
2860 */
2861 if (!set_timer(actx, actx->authz.interval * 1000))
2862 goto error_return;
2863
2864 /*
2865 * No Curl requests are running, so we can simplify by having
2866 * the client wait directly on the timerfd rather than the
2867 * multiplexer.
2868 */
2870
2872 actx->running = 1;
2873 break;
2874
2876 actx->errctx = "failed to obtain access token";
2877 if (!start_token_request(actx, conn))
2878 goto error_return;
2879
2881 break;
2882 }
2883
2884 /*
2885 * The vast majority of the time, if we don't have a token at this
2886 * point, actx->running will be set. But there are some corner cases
2887 * where we can immediately loop back around; see start_request().
2888 */
2889 } while (!oauth_token && !actx->running);
2890
2891 /* If we've stored a token, we're done. Otherwise come back later. */
2892 return oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
2893
2894error_return:
2896
2897 /*
2898 * Assemble the three parts of our error: context, body, and detail. See
2899 * also the documentation for struct async_ctx.
2900 */
2901 if (actx->errctx)
2903
2904 if (PQExpBufferDataBroken(actx->errbuf))
2905 appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
2906 else
2908
2909 if (actx->curl_err[0])
2910 {
2911 appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
2912
2913 /* Sometimes libcurl adds a newline to the error buffer. :( */
2914 if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
2915 {
2916 errbuf->data[errbuf->len - 2] = ')';
2917 errbuf->data[errbuf->len - 1] = '\0';
2918 errbuf->len--;
2919 }
2920 }
2921
2923
2924 return PGRES_POLLING_FAILED;
2925}
#define calloc(a, b)
Definition: header.h:55
static bool setup_multiplexer(struct async_ctx *actx)
Definition: oauth-curl.c:1173
static bool start_token_request(struct async_ctx *actx, PGconn *conn)
Definition: oauth-curl.c:2400
static bool initialize_curl(PGconn *conn)
Definition: oauth-curl.c:2585
static bool set_timer(struct async_ctx *actx, long timeout)
Definition: oauth-curl.c:1393
static int timer_expired(struct async_ctx *actx)
Definition: oauth-curl.c:1490
static PostgresPollingStatusType drive_request(struct async_ctx *actx)
Definition: oauth-curl.c:1863
static bool start_device_authz(struct async_ctx *actx, PGconn *conn)
Definition: oauth-curl.c:2312
static bool prompt_user(struct async_ctx *actx, PGconn *conn)
Definition: oauth-curl.c:2540
static bool finish_discovery(struct async_ctx *actx)
Definition: oauth-curl.c:2059
#define conn_oauth_discovery_uri(CONN)
Definition: oauth-curl.c:56
static bool start_discovery(struct async_ctx *actx, const char *discovery_uri)
Definition: oauth-curl.c:2050
static bool finish_device_authz(struct async_ctx *actx)
Definition: oauth-curl.c:2343
#define set_conn_oauth_token(CONN, VAL)
Definition: oauth-curl.c:62
static bool check_issuer(struct async_ctx *actx, PGconn *conn)
Definition: oauth-curl.c:2124
#define conn_errorMessage(CONN)
Definition: oauth-curl.c:53
static bool handle_token_response(struct async_ctx *actx, char **token)
Definition: oauth-curl.c:2479
static bool check_for_device_flow(struct async_ctx *actx)
Definition: oauth-curl.c:2169
static bool setup_curl_handles(struct async_ctx *actx)
Definition: oauth-curl.c:1641
bool oauth_unsafe_debugging_enabled(void)
Definition: oauth-utils.c:149
#define libpq_gettext(x)
Definition: oauth-utils.h:86
#define PQExpBufferDataBroken(buf)
Definition: pqexpbuffer.h:67
bool user_prompted
Definition: oauth-curl.c:278
enum OAuthStep step
Definition: oauth-curl.c:230
char curl_err[CURL_ERROR_SIZE]
Definition: oauth-curl.c:268

References appendPQExpBuffer(), appendPQExpBufferChar(), appendPQExpBufferStr(), async_ctx::authz, calloc, check_for_device_flow(), check_issuer(), conn, conn_errorMessage, conn_oauth_discovery_uri, conn_sasl_state, async_ctx::curl_err, PQExpBufferData::data, async_ctx::debugging, drive_request(), async_ctx::errbuf, async_ctx::errctx, finish_device_authz(), finish_discovery(), handle_token_response(), initialize_curl(), initPQExpBuffer(), device_authz::interval, PQExpBufferData::len, libpq_append_conn_error(), libpq_gettext, async_ctx::mux, OAUTH_STEP_DEVICE_AUTHORIZATION, OAUTH_STEP_DISCOVERY, OAUTH_STEP_INIT, OAUTH_STEP_TOKEN_REQUEST, OAUTH_STEP_WAIT_INTERVAL, oauth_unsafe_debugging_enabled(), PGINVALID_SOCKET, PGRES_POLLING_FAILED, PGRES_POLLING_OK, PGRES_POLLING_READING, PQExpBufferDataBroken, prompt_user(), async_ctx::running, set_conn_altsock, set_conn_oauth_token, 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 2540 of file oauth-curl.c.

2541{
2542 int res;
2543 PGpromptOAuthDevice prompt = {
2545 .user_code = actx->authz.user_code,
2546 .verification_uri_complete = actx->authz.verification_uri_complete,
2547 .expires_in = actx->authz.expires_in,
2548 };
2550
2551 res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
2552
2553 if (!res)
2554 {
2555 /*
2556 * translator: The first %s is a URL for the user to visit in a
2557 * browser, and the second %s is a code to be copy-pasted there.
2558 */
2559 fprintf(stderr, libpq_gettext("Visit %s and enter the code: %s\n"),
2560 prompt.verification_uri, prompt.user_code);
2561 }
2562 else if (res < 0)
2563 {
2564 actx_error(actx, "device prompt failed");
2565 return false;
2566 }
2567
2568 return true;
2569}
PQauthDataHook_type PQgetAuthDataHook(void)
Definition: fe-auth.c:1589
int(* PQauthDataHook_type)(PGauthData type, PGconn *conn, void *data)
Definition: libpq-fe.h:811
@ PQAUTHDATA_PROMPT_OAUTH_DEVICE
Definition: libpq-fe.h:194
const char * verification_uri
Definition: libpq-fe.h:738
const char * user_code
Definition: libpq-fe.h:739

References actx_error, async_ctx::authz, conn, device_authz::expires_in, fprintf, libpq_gettext, PQAUTHDATA_PROMPT_OAUTH_DEVICE, PQgetAuthDataHook(), 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 1099 of file oauth-curl.c.

1100{
1101 if (err->error_description)
1102 appendPQExpBuffer(&actx->errbuf, "%s ", err->error_description);
1103 else
1104 {
1105 /*
1106 * Try to get some more helpful detail into the error string. A 401
1107 * status in particular implies that the oauth_client_secret is
1108 * missing or wrong.
1109 */
1110 long response_code;
1111
1112 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, response_code = 0);
1113
1114 if (response_code == 401)
1115 {
1116 actx_error(actx, actx->used_basic_auth
1117 ? "provider rejected the oauth_client_secret"
1118 : "provider requires client authentication, and no oauth_client_secret is set");
1119 actx_error_str(actx, " ");
1120 }
1121 }
1122
1123 appendPQExpBuffer(&actx->errbuf, "(%s)", err->error);
1124}

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 1232 of file oauth-curl.c.

1234{
1235 struct async_ctx *actx = ctx;
1236
1237#if defined(HAVE_SYS_EPOLL_H)
1238 struct epoll_event ev = {0};
1239 int res;
1240 int op = EPOLL_CTL_ADD;
1241
1242 switch (what)
1243 {
1244 case CURL_POLL_IN:
1245 ev.events = EPOLLIN;
1246 break;
1247
1248 case CURL_POLL_OUT:
1249 ev.events = EPOLLOUT;
1250 break;
1251
1252 case CURL_POLL_INOUT:
1253 ev.events = EPOLLIN | EPOLLOUT;
1254 break;
1255
1256 case CURL_POLL_REMOVE:
1257 op = EPOLL_CTL_DEL;
1258 break;
1259
1260 default:
1261 actx_error(actx, "unknown libcurl socket operation: %d", what);
1262 return -1;
1263 }
1264
1265 res = epoll_ctl(actx->mux, op, socket, &ev);
1266 if (res < 0 && errno == EEXIST)
1267 {
1268 /* We already had this socket in the poll set. */
1269 op = EPOLL_CTL_MOD;
1270 res = epoll_ctl(actx->mux, op, socket, &ev);
1271 }
1272
1273 if (res < 0)
1274 {
1275 switch (op)
1276 {
1277 case EPOLL_CTL_ADD:
1278 actx_error(actx, "could not add to epoll set: %m");
1279 break;
1280
1281 case EPOLL_CTL_DEL:
1282 actx_error(actx, "could not delete from epoll set: %m");
1283 break;
1284
1285 default:
1286 actx_error(actx, "could not update epoll set: %m");
1287 }
1288
1289 return -1;
1290 }
1291
1292 return 0;
1293#elif defined(HAVE_SYS_EVENT_H)
1294 struct kevent ev[2] = {0};
1295 struct kevent ev_out[2];
1296 struct timespec timeout = {0};
1297 int nev = 0;
1298 int res;
1299
1300 switch (what)
1301 {
1302 case CURL_POLL_IN:
1303 EV_SET(&ev[nev], socket, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, 0);
1304 nev++;
1305 break;
1306
1307 case CURL_POLL_OUT:
1308 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_ADD | EV_RECEIPT, 0, 0, 0);
1309 nev++;
1310 break;
1311
1312 case CURL_POLL_INOUT:
1313 EV_SET(&ev[nev], socket, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, 0);
1314 nev++;
1315 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_ADD | EV_RECEIPT, 0, 0, 0);
1316 nev++;
1317 break;
1318
1319 case CURL_POLL_REMOVE:
1320
1321 /*
1322 * We don't know which of these is currently registered, perhaps
1323 * both, so we try to remove both. This means we need to tolerate
1324 * ENOENT below.
1325 */
1326 EV_SET(&ev[nev], socket, EVFILT_READ, EV_DELETE | EV_RECEIPT, 0, 0, 0);
1327 nev++;
1328 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_DELETE | EV_RECEIPT, 0, 0, 0);
1329 nev++;
1330 break;
1331
1332 default:
1333 actx_error(actx, "unknown libcurl socket operation: %d", what);
1334 return -1;
1335 }
1336
1337 res = kevent(actx->mux, ev, nev, ev_out, lengthof(ev_out), &timeout);
1338 if (res < 0)
1339 {
1340 actx_error(actx, "could not modify kqueue: %m");
1341 return -1;
1342 }
1343
1344 /*
1345 * We can't use the simple errno version of kevent, because we need to
1346 * skip over ENOENT while still allowing a second change to be processed.
1347 * So we need a longer-form error checking loop.
1348 */
1349 for (int i = 0; i < res; ++i)
1350 {
1351 /*
1352 * EV_RECEIPT should guarantee one EV_ERROR result for every change,
1353 * whether successful or not. Failed entries contain a non-zero errno
1354 * in the data field.
1355 */
1356 Assert(ev_out[i].flags & EV_ERROR);
1357
1358 errno = ev_out[i].data;
1359 if (errno && errno != ENOENT)
1360 {
1361 switch (what)
1362 {
1363 case CURL_POLL_REMOVE:
1364 actx_error(actx, "could not delete from kqueue: %m");
1365 break;
1366 default:
1367 actx_error(actx, "could not add to kqueue: %m");
1368 }
1369 return -1;
1370 }
1371 }
1372
1373 return 0;
1374#else
1375#error register_socket is not implemented on this platform
1376#endif
1377}
#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 1534 of file oauth-curl.c.

1535{
1536 struct async_ctx *actx = ctx;
1537
1538 /*
1539 * There might be an optimization opportunity here: if timeout == 0, we
1540 * could signal drive_request to immediately call
1541 * curl_multi_socket_action, rather than returning all the way up the
1542 * stack only to come right back. But it's not clear that the additional
1543 * code complexity is worth it.
1544 */
1545 if (!set_timer(actx, timeout))
1546 return -1; /* actx_error already called */
1547
1548 return 0;
1549}

References set_timer().

Referenced by setup_curl_handles().

◆ report_type_mismatch()

static void report_type_mismatch ( struct oauth_parse ctx)
static

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

465{
466 char *msgfmt;
467
468 Assert(ctx->active);
469
470 /*
471 * At the moment, the only fields we're interested in are strings,
472 * numbers, and arrays of strings.
473 */
474 switch (ctx->active->type)
475 {
477 msgfmt = "field \"%s\" must be a string";
478 break;
479
481 msgfmt = "field \"%s\" must be a number";
482 break;
483
485 msgfmt = "field \"%s\" must be an array of strings";
486 break;
487
488 default:
489 Assert(false);
490 msgfmt = "field \"%s\" has unexpected type";
491 }
492
493 oauth_parse_set_error(ctx, msgfmt, ctx->active->name);
494}

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 1393 of file oauth-curl.c.

1394{
1395#if defined(HAVE_SYS_EPOLL_H)
1396 struct itimerspec spec = {0};
1397
1398 if (timeout < 0)
1399 {
1400 /* the zero itimerspec will disarm the timer below */
1401 }
1402 else if (timeout == 0)
1403 {
1404 /*
1405 * A zero timeout means libcurl wants us to call back immediately.
1406 * That's not technically an option for timerfd, but we can make the
1407 * timeout ridiculously short.
1408 */
1409 spec.it_value.tv_nsec = 1;
1410 }
1411 else
1412 {
1413 spec.it_value.tv_sec = timeout / 1000;
1414 spec.it_value.tv_nsec = (timeout % 1000) * 1000000;
1415 }
1416
1417 if (timerfd_settime(actx->timerfd, 0 /* no flags */ , &spec, NULL) < 0)
1418 {
1419 actx_error(actx, "setting timerfd to %ld: %m", timeout);
1420 return false;
1421 }
1422
1423 return true;
1424#elif defined(HAVE_SYS_EVENT_H)
1425 struct kevent ev;
1426
1427#ifdef __NetBSD__
1428
1429 /*
1430 * Work around NetBSD's rejection of zero timeouts (EINVAL), a bit like
1431 * timerfd above.
1432 */
1433 if (timeout == 0)
1434 timeout = 1;
1435#endif
1436
1437 /*
1438 * Always disable the timer, and remove it from the multiplexer, to clear
1439 * out any already-queued events. (On some BSDs, adding an EVFILT_TIMER to
1440 * a kqueue that already has one will clear stale events, but not on
1441 * macOS.)
1442 *
1443 * If there was no previous timer set, the kevent calls will result in
1444 * ENOENT, which is fine.
1445 */
1446 EV_SET(&ev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, 0);
1447 if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1448 {
1449 actx_error(actx, "deleting kqueue timer: %m");
1450 return false;
1451 }
1452
1453 EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_DELETE, 0, 0, 0);
1454 if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1455 {
1456 actx_error(actx, "removing kqueue timer from multiplexer: %m");
1457 return false;
1458 }
1459
1460 /* If we're not adding a timer, we're done. */
1461 if (timeout < 0)
1462 return true;
1463
1464 EV_SET(&ev, 1, EVFILT_TIMER, (EV_ADD | EV_ONESHOT), 0, timeout, 0);
1465 if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0)
1466 {
1467 actx_error(actx, "setting kqueue timer to %ld: %m", timeout);
1468 return false;
1469 }
1470
1471 EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_ADD, 0, 0, 0);
1472 if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0)
1473 {
1474 actx_error(actx, "adding kqueue timer to multiplexer: %m");
1475 return false;
1476 }
1477
1478 return true;
1479#else
1480#error set_timer is not implemented on this platform
1481#endif
1482}

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 1641 of file oauth-curl.c.

1642{
1643 /*
1644 * Create our multi handle. This encapsulates the entire conversation with
1645 * libcurl for this connection.
1646 */
1647 actx->curlm = curl_multi_init();
1648 if (!actx->curlm)
1649 {
1650 /* We don't get a lot of feedback on the failure reason. */
1651 actx_error(actx, "failed to create libcurl multi handle");
1652 return false;
1653 }
1654
1655 /*
1656 * The multi handle tells us what to wait on using two callbacks. These
1657 * will manipulate actx->mux as needed.
1658 */
1659 CHECK_MSETOPT(actx, CURLMOPT_SOCKETFUNCTION, register_socket, return false);
1660 CHECK_MSETOPT(actx, CURLMOPT_SOCKETDATA, actx, return false);
1661 CHECK_MSETOPT(actx, CURLMOPT_TIMERFUNCTION, register_timer, return false);
1662 CHECK_MSETOPT(actx, CURLMOPT_TIMERDATA, actx, return false);
1663
1664 /*
1665 * Set up an easy handle. All of our requests are made serially, so we
1666 * only ever need to keep track of one.
1667 */
1668 actx->curl = curl_easy_init();
1669 if (!actx->curl)
1670 {
1671 actx_error(actx, "failed to create libcurl handle");
1672 return false;
1673 }
1674
1675 /*
1676 * Multi-threaded applications must set CURLOPT_NOSIGNAL. This requires us
1677 * to handle the possibility of SIGPIPE ourselves using pq_block_sigpipe;
1678 * see pg_fe_run_oauth_flow().
1679 *
1680 * NB: If libcurl is not built against a friendly DNS resolver (c-ares or
1681 * threaded), setting this option prevents DNS lookups from timing out
1682 * correctly. We warn about this situation at configure time.
1683 *
1684 * TODO: Perhaps there's a clever way to warn the user about synchronous
1685 * DNS at runtime too? It's not immediately clear how to do that in a
1686 * helpful way: for many standard single-threaded use cases, the user
1687 * might not care at all, so spraying warnings to stderr would probably do
1688 * more harm than good.
1689 */
1690 CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L, return false);
1691
1692 if (actx->debugging)
1693 {
1694 /*
1695 * Set a callback for retrieving error information from libcurl, the
1696 * function only takes effect when CURLOPT_VERBOSE has been set so
1697 * make sure the order is kept.
1698 */
1699 CHECK_SETOPT(actx, CURLOPT_DEBUGFUNCTION, debug_callback, return false);
1700 CHECK_SETOPT(actx, CURLOPT_VERBOSE, 1L, return false);
1701 }
1702
1703 CHECK_SETOPT(actx, CURLOPT_ERRORBUFFER, actx->curl_err, return false);
1704
1705 /*
1706 * Only HTTPS is allowed. (Debug mode additionally allows HTTP; this is
1707 * intended for testing only.)
1708 *
1709 * There's a bit of unfortunate complexity around the choice of
1710 * CURLoption. CURLOPT_PROTOCOLS is deprecated in modern Curls, but its
1711 * replacement didn't show up until relatively recently.
1712 */
1713 {
1714#if CURL_AT_LEAST_VERSION(7, 85, 0)
1715 const CURLoption popt = CURLOPT_PROTOCOLS_STR;
1716 const char *protos = "https";
1717 const char *const unsafe = "https,http";
1718#else
1719 const CURLoption popt = CURLOPT_PROTOCOLS;
1720 long protos = CURLPROTO_HTTPS;
1721 const long unsafe = CURLPROTO_HTTPS | CURLPROTO_HTTP;
1722#endif
1723
1724 if (actx->debugging)
1725 protos = unsafe;
1726
1727 CHECK_SETOPT(actx, popt, protos, return false);
1728 }
1729
1730 /*
1731 * If we're in debug mode, allow the developer to change the trusted CA
1732 * list. For now, this is not something we expose outside of the UNSAFE
1733 * mode, because it's not clear that it's useful in production: both libpq
1734 * and the user's browser must trust the same authorization servers for
1735 * the flow to work at all, so any changes to the roots are likely to be
1736 * done system-wide.
1737 */
1738 if (actx->debugging)
1739 {
1740 const char *env;
1741
1742 if ((env = getenv("PGOAUTHCAFILE")) != NULL)
1743 CHECK_SETOPT(actx, CURLOPT_CAINFO, env, return false);
1744 }
1745
1746 /*
1747 * Suppress the Accept header to make our request as minimal as possible.
1748 * (Ideally we would set it to "application/json" instead, but OpenID is
1749 * pretty strict when it comes to provider behavior, so we have to check
1750 * what comes back anyway.)
1751 */
1752 actx->headers = curl_slist_append(actx->headers, "Accept:");
1753 if (actx->headers == NULL)
1754 {
1755 actx_error(actx, "out of memory");
1756 return false;
1757 }
1758 CHECK_SETOPT(actx, CURLOPT_HTTPHEADER, actx->headers, return false);
1759
1760 return true;
1761}
static int register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx, void *socketp)
Definition: oauth-curl.c:1232
#define CHECK_MSETOPT(ACTX, OPT, VAL, FAILACTION)
Definition: oauth-curl.c:384
static int debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *clientp)
Definition: oauth-curl.c:1558
static int register_timer(CURLM *curlm, long timeout, void *ctx)
Definition: oauth-curl.c:1534

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 1173 of file oauth-curl.c.

1174{
1175#if defined(HAVE_SYS_EPOLL_H)
1176 struct epoll_event ev = {.events = EPOLLIN};
1177
1178 actx->mux = epoll_create1(EPOLL_CLOEXEC);
1179 if (actx->mux < 0)
1180 {
1181 actx_error(actx, "failed to create epoll set: %m");
1182 return false;
1183 }
1184
1185 actx->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
1186 if (actx->timerfd < 0)
1187 {
1188 actx_error(actx, "failed to create timerfd: %m");
1189 return false;
1190 }
1191
1192 if (epoll_ctl(actx->mux, EPOLL_CTL_ADD, actx->timerfd, &ev) < 0)
1193 {
1194 actx_error(actx, "failed to add timerfd to epoll set: %m");
1195 return false;
1196 }
1197
1198 return true;
1199#elif defined(HAVE_SYS_EVENT_H)
1200 actx->mux = kqueue();
1201 if (actx->mux < 0)
1202 {
1203 /*- translator: the term "kqueue" (kernel queue) should not be translated */
1204 actx_error(actx, "failed to create kqueue: %m");
1205 return false;
1206 }
1207
1208 /*
1209 * Originally, we set EVFILT_TIMER directly on the top-level multiplexer.
1210 * This makes it difficult to implement timer_expired(), though, so now we
1211 * set EVFILT_TIMER on a separate actx->timerfd, which is chained to
1212 * actx->mux while the timer is active.
1213 */
1214 actx->timerfd = kqueue();
1215 if (actx->timerfd < 0)
1216 {
1217 actx_error(actx, "failed to create timer kqueue: %m");
1218 return false;
1219 }
1220
1221 return true;
1222#else
1223#error setup_multiplexer is not implemented on this platform
1224#endif
1225}

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 2312 of file oauth-curl.c.

2313{
2314 const char *oauth_scope = conn_oauth_scope(conn);
2315 const char *device_authz_uri = actx->provider.device_authorization_endpoint;
2316 PQExpBuffer work_buffer = &actx->work_data;
2317
2318 Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
2319 Assert(device_authz_uri); /* ensured by check_for_device_flow() */
2320
2321 /* Construct our request body. */
2322 resetPQExpBuffer(work_buffer);
2323 if (oauth_scope && oauth_scope[0])
2324 build_urlencoded(work_buffer, "scope", oauth_scope);
2325
2326 if (!add_client_identification(actx, work_buffer, conn))
2327 return false;
2328
2329 if (PQExpBufferBroken(work_buffer))
2330 {
2331 actx_error(actx, "out of memory");
2332 return false;
2333 }
2334
2335 /* Make our request. */
2336 CHECK_SETOPT(actx, CURLOPT_URL, device_authz_uri, return false);
2337 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2338
2339 return start_request(actx);
2340}
static bool add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
Definition: oauth-curl.c:2228
#define conn_oauth_scope(CONN)
Definition: oauth-curl.c:58
static bool start_request(struct async_ctx *actx)
Definition: oauth-curl.c:1814
void resetPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:146

References actx_error, add_client_identification(), Assert(), build_urlencoded(), CHECK_SETOPT, conn, conn_oauth_client_id, conn_oauth_scope, PQExpBufferData::data, provider::device_authorization_endpoint, 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 2050 of file oauth-curl.c.

2051{
2052 CHECK_SETOPT(actx, CURLOPT_HTTPGET, 1L, return false);
2053 CHECK_SETOPT(actx, CURLOPT_URL, discovery_uri, return false);
2054
2055 return start_request(actx);
2056}

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 1814 of file oauth-curl.c.

1815{
1816 CURLMcode err;
1817
1819 CHECK_SETOPT(actx, CURLOPT_WRITEFUNCTION, append_data, return false);
1820 CHECK_SETOPT(actx, CURLOPT_WRITEDATA, actx, return false);
1821
1822 err = curl_multi_add_handle(actx->curlm, actx->curl);
1823 if (err)
1824 {
1825 actx_error(actx, "failed to queue HTTP request: %s",
1826 curl_multi_strerror(err));
1827 return false;
1828 }
1829
1830 /*
1831 * actx->running tracks the number of running handles, so we can
1832 * immediately call back if no waiting is needed.
1833 *
1834 * Even though this is nominally an asynchronous process, there are some
1835 * operations that can synchronously fail by this point (e.g. connections
1836 * to closed local ports) or even synchronously succeed if the stars align
1837 * (all the libcurl connection caches hit and the server is fast).
1838 */
1839 err = curl_multi_socket_action(actx->curlm, CURL_SOCKET_TIMEOUT, 0, &actx->running);
1840 if (err)
1841 {
1842 actx_error(actx, "asynchronous HTTP request failed: %s",
1843 curl_multi_strerror(err));
1844 return false;
1845 }
1846
1847 return true;
1848}
static size_t append_data(char *buf, size_t size, size_t nmemb, void *userdata)
Definition: oauth-curl.c:1774

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 2400 of file oauth-curl.c.

2401{
2402 const char *token_uri = actx->provider.token_endpoint;
2403 const char *device_code = actx->authz.device_code;
2404 PQExpBuffer work_buffer = &actx->work_data;
2405
2406 Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
2407 Assert(token_uri); /* ensured by parse_provider() */
2408 Assert(device_code); /* ensured by parse_device_authz() */
2409
2410 /* Construct our request body. */
2411 resetPQExpBuffer(work_buffer);
2412 build_urlencoded(work_buffer, "device_code", device_code);
2413 build_urlencoded(work_buffer, "grant_type", OAUTH_GRANT_TYPE_DEVICE_CODE);
2414
2415 if (!add_client_identification(actx, work_buffer, conn))
2416 return false;
2417
2418 if (PQExpBufferBroken(work_buffer))
2419 {
2420 actx_error(actx, "out of memory");
2421 return false;
2422 }
2423
2424 /* Make our request. */
2425 CHECK_SETOPT(actx, CURLOPT_URL, token_uri, return false);
2426 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2427
2428 return start_request(actx);
2429}
#define OAUTH_GRANT_TYPE_DEVICE_CODE
Definition: oauth-curl.c:2161

References actx_error, add_client_identification(), Assert(), async_ctx::authz, build_urlencoded(), CHECK_SETOPT, conn, conn_oauth_client_id, PQExpBufferData::data, device_authz::device_code, 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 1490 of file oauth-curl.c.

1491{
1492#if defined(HAVE_SYS_EPOLL_H)
1493 struct itimerspec spec = {0};
1494
1495 if (timerfd_gettime(actx->timerfd, &spec) < 0)
1496 {
1497 actx_error(actx, "getting timerfd value: %m");
1498 return -1;
1499 }
1500
1501 /*
1502 * This implementation assumes we're using single-shot timers. If you
1503 * change to using intervals, you'll need to reimplement this function
1504 * too, possibly with the read() or select() interfaces for timerfd.
1505 */
1506 Assert(spec.it_interval.tv_sec == 0
1507 && spec.it_interval.tv_nsec == 0);
1508
1509 /* If the remaining time to expiration is zero, we're done. */
1510 return (spec.it_value.tv_sec == 0
1511 && spec.it_value.tv_nsec == 0);
1512#elif defined(HAVE_SYS_EVENT_H)
1513 int res;
1514
1515 /* Is the timer queue ready? */
1516 res = PQsocketPoll(actx->timerfd, 1 /* forRead */ , 0, 0);
1517 if (res < 0)
1518 {
1519 actx_error(actx, "checking kqueue for timeout: %m");
1520 return -1;
1521 }
1522
1523 return (res > 0);
1524#else
1525#error timer_expired is not implemented on this platform
1526#endif
1527}
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 2006 of file oauth-curl.c.

2007{
2009
2012
2013 return PQExpBufferDataBroken(buf) ? NULL : buf.data;
2014}

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

Referenced by add_client_identification().