PostgreSQL Source Code git master
Data Structures | Macros | Enumerations | Functions
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:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

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

Macros

#define  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

 

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)
 
 
static void  report_type_mismatch (struct oauth_parse *ctx)
 
 
static JsonParseErrorType  oauth_json_object_field_start (void *state, char *name, bool isnull)
 
 
 
 
 
static bool  check_content_type (struct async_ctx *actx, const char *type)
 
static bool  parse_oauth_json (struct async_ctx *actx, const struct json_field *fields)
 
static bool  parse_provider (struct async_ctx *actx, struct provider *provider)
 
static double  parse_json_number (const char *s)
 
static int  parse_interval (struct async_ctx *actx, const char *interval_str)
 
static int  parse_expires_in (struct async_ctx *actx, const char *expires_in_str)
 
static bool  parse_device_authz (struct async_ctx *actx, struct device_authz *authz)
 
static bool  parse_token_error (struct async_ctx *actx, struct token_error *err)
 
static void  record_token_error (struct async_ctx *actx, const struct token_error *err)
 
static bool  parse_access_token (struct async_ctx *actx, struct token *tok)
 
static bool  setup_multiplexer (struct async_ctx *actx)
 
static int  register_socket (CURL *curl, curl_socket_t socket, int what, void *ctx, void *socketp)
 
static bool  comb_multiplexer (struct async_ctx *actx)
 
static bool  set_timer (struct async_ctx *actx, long timeout)
 
static int  timer_expired (struct async_ctx *actx)
 
static int  register_timer (CURLM *curlm, long timeout, void *ctx)
 
static bool  drain_timer_events (struct async_ctx *actx, bool *was_expired)
 
static int  debug_callback (CURL *handle, curl_infotype type, char *data, size_t size, void *clientp)
 
static bool  setup_curl_handles (struct async_ctx *actx)
 
static size_t  append_data (char *buf, size_t size, size_t nmemb, void *userdata)
 
static bool  start_request (struct async_ctx *actx)
 
 
static 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)
 
 
 

Macro Definition Documentation

actx_error

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

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

actx_error_str

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

Definition at line 377 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 407 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 385 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 396 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 1919 of file oauth-curl.c.

HTTPS_SCHEME

#define HTTPS_SCHEME   "https://"

Definition at line 2224 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 2225 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 461 of file oauth-curl.c.

PG_OAUTH_OPTIONAL

#define PG_OAUTH_OPTIONAL   false

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

PG_OAUTH_REQUIRED

#define PG_OAUTH_REQUIRED   true

Definition at line 448 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_ctxactx,
PQExpBuffer  reqbody,
PGconnconn 
)
static

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

2293{
2294 const char *oauth_client_id = conn_oauth_client_id(conn);
2295 const char *oauth_client_secret = conn_oauth_client_secret(conn);
2296
2297 bool success = false;
2298 char *username = NULL;
2299 char *password = NULL;
2300
2301 if (oauth_client_secret) /* Zero-length secrets are permitted! */
2302 {
2303 /*----
2304 * Use HTTP Basic auth to send the client_id and secret. Per RFC 6749,
2305 * Sec. 2.3.1,
2306 *
2307 * Including the client credentials in the request-body using the
2308 * two parameters is NOT RECOMMENDED and SHOULD be limited to
2309 * clients unable to directly utilize the HTTP Basic authentication
2310 * scheme (or other password-based HTTP authentication schemes).
2311 *
2312 * Additionally:
2313 *
2314 * The client identifier is encoded using the
2315 * "application/x-www-form-urlencoded" encoding algorithm per Appendix
2316 * B, and the encoded value is used as the username; the client
2317 * password is encoded using the same algorithm and used as the
2318 * password.
2319 *
2320 * (Appendix B modifies application/x-www-form-urlencoded by requiring
2321 * an initial UTF-8 encoding step. Since the client ID and secret must
2322 * both be 7-bit ASCII -- RFC 6749 Appendix A -- we don't worry about
2323 * that in this function.)
2324 *
2325 * client_id is not added to the request body in this case. Not only
2326 * would it be redundant, but some providers in the wild (e.g. Okta)
2327 * refuse to accept it.
2328 */
2329 username = urlencode(oauth_client_id);
2330 password = urlencode(oauth_client_secret);
2331
2332 if (!username || !password)
2333 {
2334 actx_error(actx, "out of memory");
2335 goto cleanup;
2336 }
2337
2338 CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_BASIC, goto cleanup);
2339 CHECK_SETOPT(actx, CURLOPT_USERNAME, username, goto cleanup);
2340 CHECK_SETOPT(actx, CURLOPT_PASSWORD, password, goto cleanup);
2341
2342 actx->used_basic_auth = true;
2343 }
2344 else
2345 {
2346 /*
2347 * If we're not otherwise authenticating, client_id is REQUIRED in the
2348 * request body.
2349 */
2350 build_urlencoded(reqbody, "client_id", oauth_client_id);
2351
2352 CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, goto cleanup);
2353 actx->used_basic_auth = false;
2354 }
2355
2356 success = true;
2357
2358cleanup:
2359 free(username);
2360 free(password);
2361
2362 return success;
2363}
static void cleanup(void)
Definition: bootstrap.c:715
#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:2070
static void build_urlencoded(PQExpBuffer buf, const char *key, const char *value)
Definition: oauth-curl.c:2085
#define CHECK_SETOPT(ACTX, OPT, VAL, FAILACTION)
Definition: oauth-curl.c:396
#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:374
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 1838 of file oauth-curl.c.

1839{
1840 struct async_ctx *actx = userdata;
1841 PQExpBuffer resp = &actx->work_data;
1842 size_t len = size * nmemb;
1843
1844 /* In case we receive data over the threshold, abort the transfer */
1845 if ((resp->len + len) > MAX_OAUTH_RESPONSE_SIZE)
1846 {
1847 actx_error(actx, "response is too large");
1848 return 0;
1849 }
1850
1851 /* The data passed from libcurl is not null-terminated */
1853
1854 /*
1855 * Signal an error in order to abort the transfer in case we ran out of
1856 * memory in accepting the data.
1857 */
1858 if (PQExpBufferBroken(resp))
1859 {
1860 actx_error(actx, "out of memory");
1861 return 0;
1862 }
1863
1864 return len;
1865}
#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
size_t len
Definition: pqexpbuffer.h:47
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 start_request().

append_urlencoded()

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

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

2028{
2029 char *escaped;
2030 char *haystack;
2031 char *match;
2032
2033 /* The first parameter to curl_easy_escape is deprecated by Curl */
2034 escaped = curl_easy_escape(NULL, s, 0);
2035 if (!escaped)
2036 {
2037 termPQExpBuffer(buf); /* mark the buffer broken */
2038 return;
2039 }
2040
2041 /*
2042 * curl_easy_escape() almost does what we want, but we need the
2043 * query-specific flavor which uses '+' instead of '%20' for spaces. The
2044 * Curl command-line tool does this with a simple search-and-replace, so
2045 * follow its lead.
2046 */
2047 haystack = escaped;
2048
2049 while ((match = strstr(haystack, "%20")) != NULL)
2050 {
2051 /* Append the unmatched portion, followed by the plus sign. */
2052 appendBinaryPQExpBuffer(buf, haystack, match - haystack);
2054
2055 /* Keep searching after the match. */
2056 haystack = match + 3 /* strlen("%20") */ ;
2057 }
2058
2059 /* Push the remainder of the string onto the buffer. */
2060 appendPQExpBufferStr(buf, haystack);
2061
2062 curl_free(escaped);
2063}
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 2085 of file oauth-curl.c.

2086{
2087 if (buf->len)
2089
2093}
static struct @169 value
static void append_urlencoded(PQExpBuffer buf, const char *s)
Definition: oauth-curl.c:2027

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_ctxactx,
const char *  type 
)
static

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

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

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

2234{
2235 const struct provider *provider = &actx->provider;
2236
2237 Assert(provider->issuer); /* ensured by parse_provider() */
2238 Assert(provider->token_endpoint); /* ensured by parse_provider() */
2239
2241 {
2242 actx_error(actx,
2243 "issuer \"%s\" does not provide a device authorization endpoint",
2244 provider->issuer);
2245 return false;
2246 }
2247
2248 /*
2249 * The original implementation checked that OAUTH_GRANT_TYPE_DEVICE_CODE
2250 * was present in the discovery document's grant_types_supported list. MS
2251 * Entra does not advertise this grant type, though, and since it doesn't
2252 * make sense to stand up a device_authorization_endpoint without also
2253 * accepting device codes at the token_endpoint, that's the only thing we
2254 * currently require.
2255 */
2256
2257 /*
2258 * Although libcurl will fail later if the URL contains an unsupported
2259 * scheme, that error message is going to be a bit opaque. This is a
2260 * decent time to bail out if we're not using HTTPS for the endpoints
2261 * we'll use for the flow.
2262 */
2263 if (!actx->debugging)
2264 {
2266 HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
2267 {
2268 actx_error(actx,
2269 "device authorization endpoint \"%s\" must use HTTPS",
2271 return false;
2272 }
2273
2275 HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
2276 {
2277 actx_error(actx,
2278 "token endpoint \"%s\" must use HTTPS",
2280 return false;
2281 }
2282 }
2283
2284 return true;
2285}
#define HTTPS_SCHEME
Definition: oauth-curl.c:2224
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_ctxactx,
PGconnconn 
)
static

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

2189{
2190 const struct provider *provider = &actx->provider;
2191 const char *oauth_issuer_id = conn_oauth_issuer_id(conn);
2192
2193 Assert(oauth_issuer_id); /* ensured by setup_oauth_parameters() */
2194 Assert(provider->issuer); /* ensured by parse_provider() */
2195
2196 /*---
2197 * We require strict equality for issuer identifiers -- no path or case
2198 * normalization, no substitution of default ports and schemes, etc. This
2199 * is done to match the rules in OIDC Discovery Sec. 4.3 for config
2200 * validation:
2201 *
2202 * The issuer value returned MUST be identical to the Issuer URL that
2203 * was used as the prefix to /.well-known/openid-configuration to
2204 * retrieve the configuration information.
2205 *
2206 * as well as the rules set out in RFC 9207 for avoiding mix-up attacks:
2207 *
2208 * Clients MUST then [...] compare the result to the issuer identifier
2209 * of the authorization server where the authorization request was
2210 * sent to. This comparison MUST use simple string comparison as defined
2211 * in Section 6.2.1 of [RFC3986].
2212 */
2213 if (strcmp(oauth_issuer_id, provider->issuer) != 0)
2214 {
2215 actx_error(actx,
2216 "the issuer identifier (%s) does not match oauth_issuer (%s)",
2217 provider->issuer, oauth_issuer_id);
2218 return false;
2219 }
2220
2221 return true;
2222}
#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().

comb_multiplexer()

static bool comb_multiplexer ( struct async_ctxactx )
static

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

1400{
1401#if defined(HAVE_SYS_EPOLL_H)
1402 /* The epoll implementation doesn't hold onto stale events. */
1403 return true;
1404#elif defined(HAVE_SYS_EVENT_H)
1405 struct timespec timeout = {0};
1406 struct kevent ev;
1407
1408 /*
1409 * Try to read a single pending event. We can actually ignore the result:
1410 * either we found an event to process, in which case the multiplexer is
1411 * correctly readable for that event at minimum, and it doesn't matter if
1412 * there are any stale events; or we didn't find any, in which case the
1413 * kernel will have discarded any stale events as it traveled to the end
1414 * of the queue.
1415 *
1416 * Note that this depends on our registrations being level-triggered --
1417 * even the timer, so we use a chained kqueue for that instead of an
1418 * EVFILT_TIMER on the top-level mux. If we used edge-triggered events,
1419 * this call would improperly discard them.
1420 */
1421 if (kevent(actx->mux, NULL, 0, &ev, 1, &timeout) < 0)
1422 {
1423 actx_error(actx, "could not comb kqueue: %m");
1424 return false;
1425 }
1426
1427 return true;
1428#else
1429#error comb_multiplexer is not implemented on this platform
1430#endif
1431}
pgsocket mux
Definition: oauth-curl.c:233

References actx_error, and async_ctx::mux.

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

1624{
1625 const char *prefix;
1626 bool printed_prefix = false;
1628
1629 /* Prefixes are modeled off of the default libcurl debug output. */
1630 switch (type)
1631 {
1632 case CURLINFO_TEXT:
1633 prefix = "*";
1634 break;
1635
1636 case CURLINFO_HEADER_IN: /* fall through */
1637 case CURLINFO_DATA_IN:
1638 prefix = "<";
1639 break;
1640
1641 case CURLINFO_HEADER_OUT: /* fall through */
1642 case CURLINFO_DATA_OUT:
1643 prefix = ">";
1644 break;
1645
1646 default:
1647 return 0;
1648 }
1649
1651
1652 /*
1653 * Split the output into lines for readability; sometimes multiple headers
1654 * are included in a single call. We also don't allow unprintable ASCII
1655 * through without a basic <XX> escape.
1656 */
1657 for (int i = 0; i < size; i++)
1658 {
1659 char c = data[i];
1660
1661 if (!printed_prefix)
1662 {
1663 appendPQExpBuffer(&buf, "[libcurl] %s ", prefix);
1664 printed_prefix = true;
1665 }
1666
1667 if (c >= 0x20 && c <= 0x7E)
1669 else if ((type == CURLINFO_HEADER_IN
1670 || type == CURLINFO_HEADER_OUT
1671 || type == CURLINFO_TEXT)
1672 && (c == '\r' || c == '\n'))
1673 {
1674 /*
1675 * Don't bother emitting <0D><0A> for headers and text; it's not
1676 * helpful noise.
1677 */
1678 }
1679 else
1680 appendPQExpBuffer(&buf, "<%02X>", c);
1681
1682 if (c == '\n')
1683 {
1685 printed_prefix = false;
1686 }
1687 }
1688
1689 if (printed_prefix)
1690 appendPQExpBufferChar(&buf, '\n'); /* finish the line */
1691
1692 fprintf(stderr, "%s", buf.data);
1694 return 0;
1695}
#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
c
char * c
Definition: preproc-cursor.c:31

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

Referenced by setup_curl_handles().

drain_timer_events()

static bool drain_timer_events ( struct async_ctxactx,
bool *  was_expired 
)
static

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

1591{
1592 int res;
1593
1594 res = timer_expired(actx);
1595 if (res < 0)
1596 return false;
1597
1598 if (res > 0)
1599 {
1600 /*
1601 * Timer is expired. We could drain the event manually from the
1602 * timerfd, but it's easier to simply disable it; that keeps the
1603 * platform-specific code in set_timer().
1604 */
1605 if (!set_timer(actx, -1))
1606 return false;
1607 }
1608
1609 if (was_expired)
1610 *was_expired = (res > 0);
1611
1612 return true;
1613}
static bool set_timer(struct async_ctx *actx, long timeout)
Definition: oauth-curl.c:1447
static int timer_expired(struct async_ctx *actx)
Definition: oauth-curl.c:1544

References set_timer(), and timer_expired().

Referenced by pg_fe_run_oauth_flow_impl().

drive_request()

static PostgresPollingStatusType drive_request ( struct async_ctxactx )
static

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

1928{
1929 CURLMcode err;
1930 CURLMsg *msg;
1931 int msgs_left;
1932 bool done;
1933
1934 if (actx->running)
1935 {
1936 /*---
1937 * There's an async request in progress. Pump the multi handle.
1938 *
1939 * curl_multi_socket_all() is officially deprecated, because it's
1940 * inefficient and pointless if your event loop has already handed you
1941 * the exact sockets that are ready. But that's not our use case --
1942 * our client has no way to tell us which sockets are ready. (They
1943 * don't even know there are sockets to begin with.)
1944 *
1945 * We can grab the list of triggered events from the multiplexer
1946 * ourselves, but that's effectively what curl_multi_socket_all() is
1947 * going to do. And there are currently no plans for the Curl project
1948 * to remove or break this API, so ignore the deprecation. See
1949 *
1950 * https://curl.se/mail/lib-2024-11/0028.html
1951 *
1952 */
1954 err = curl_multi_socket_all(actx->curlm, &actx->running);
1955 )
1956
1957 if (err)
1958 {
1959 actx_error(actx, "asynchronous HTTP request failed: %s",
1960 curl_multi_strerror(err));
1961 return PGRES_POLLING_FAILED;
1962 }
1963
1964 if (actx->running)
1965 {
1966 /* We'll come back again. */
1967 return PGRES_POLLING_READING;
1968 }
1969 }
1970
1971 done = false;
1972 while ((msg = curl_multi_info_read(actx->curlm, &msgs_left)) != NULL)
1973 {
1974 if (msg->msg != CURLMSG_DONE)
1975 {
1976 /*
1977 * Future libcurl versions may define new message types; we don't
1978 * know how to handle them, so we'll ignore them.
1979 */
1980 continue;
1981 }
1982
1983 /* First check the status of the request itself. */
1984 if (msg->data.result != CURLE_OK)
1985 {
1986 /*
1987 * If a more specific error hasn't already been reported, use
1988 * libcurl's description.
1989 */
1990 if (actx->errbuf.len == 0)
1991 actx_error_str(actx, curl_easy_strerror(msg->data.result));
1992
1993 return PGRES_POLLING_FAILED;
1994 }
1995
1996 /* Now remove the finished handle; we'll add it back later if needed. */
1997 err = curl_multi_remove_handle(actx->curlm, msg->easy_handle);
1998 if (err)
1999 {
2000 actx_error(actx, "libcurl easy handle removal failed: %s",
2001 curl_multi_strerror(err));
2002 return PGRES_POLLING_FAILED;
2003 }
2004
2005 done = true;
2006 }
2007
2008 /* Sanity check. */
2009 if (!done)
2010 {
2011 actx_error(actx, "no result was retrieved for the finished handle");
2012 return PGRES_POLLING_FAILED;
2013 }
2014
2015 return PGRES_POLLING_OK;
2016}
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:377
#define CURL_IGNORE_DEPRECATION(x)
Definition: oauth-curl.c:1919
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_ctxactx )
static

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

2408{
2409 long response_code;
2410
2411 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2412
2413 /*
2414 * Per RFC 8628, Section 3, a successful device authorization response
2415 * uses 200 OK.
2416 */
2417 if (response_code == 200)
2418 {
2419 actx->errctx = "failed to parse device authorization";
2420 if (!parse_device_authz(actx, &actx->authz))
2421 return false; /* error message already set */
2422
2423 return true;
2424 }
2425
2426 /*
2427 * The device authorization endpoint uses the same error response as the
2428 * token endpoint, so the error handling roughly follows
2429 * finish_token_request(). The key difference is that an error here is
2430 * immediately fatal.
2431 */
2432 if (response_code == 400 || response_code == 401)
2433 {
2434 struct token_error err = {0};
2435
2436 if (!parse_token_error(actx, &err))
2437 {
2439 return false;
2440 }
2441
2442 /* Copy the token error into the context error buffer */
2443 record_token_error(actx, &err);
2444
2446 return false;
2447 }
2448
2449 /* Any other response codes are considered invalid */
2450 actx_error(actx, "unexpected response code %ld", response_code);
2451 return false;
2452}
static bool parse_token_error(struct async_ctx *actx, struct token_error *err)
Definition: oauth-curl.c:1072
static void record_token_error(struct async_ctx *actx, const struct token_error *err)
Definition: oauth-curl.c:1100
static bool parse_device_authz(struct async_ctx *actx, struct device_authz *authz)
Definition: oauth-curl.c:1018
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_ctxactx )
static

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

2124{
2125 long response_code;
2126
2127 /*----
2128 * Now check the response. OIDC Discovery 1.0 is pretty strict:
2129 *
2130 * A successful response MUST use the 200 OK HTTP status code and
2131 * return a JSON object using the application/json content type that
2132 * contains a set of Claims as its members that are a subset of the
2133 * Metadata values defined in Section 3.
2134 *
2135 * Compared to standard HTTP semantics, this makes life easy -- we don't
2136 * need to worry about redirections (which would call the Issuer host
2137 * validation into question), or non-authoritative responses, or any other
2138 * complications.
2139 */
2140 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2141
2142 if (response_code != 200)
2143 {
2144 actx_error(actx, "unexpected response code %ld", response_code);
2145 return false;
2146 }
2147
2148 /*
2149 * Pull the fields we care about from the document.
2150 */
2151 actx->errctx = "failed to parse OpenID discovery document";
2152 if (!parse_provider(actx, &actx->provider))
2153 return false; /* error message already set */
2154
2155 /*
2156 * Fill in any defaults for OPTIONAL/RECOMMENDED fields we care about.
2157 */
2159 {
2160 /*
2161 * Per Section 3, the default is ["authorization_code", "implicit"].
2162 */
2163 struct curl_slist *temp = actx->provider.grant_types_supported;
2164
2165 temp = curl_slist_append(temp, "authorization_code");
2166 if (temp)
2167 {
2168 temp = curl_slist_append(temp, "implicit");
2169 }
2170
2171 if (!temp)
2172 {
2173 actx_error(actx, "out of memory");
2174 return false;
2175 }
2176
2177 actx->provider.grant_types_supported = temp;
2178 }
2179
2180 return true;
2181}
static bool parse_provider(struct async_ctx *actx, struct provider *provider)
Definition: oauth-curl.c:907
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_ctxactx,
struct tokentok 
)
static

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

2497{
2498 long response_code;
2499
2500 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2501
2502 /*
2503 * Per RFC 6749, Section 5, a successful response uses 200 OK.
2504 */
2505 if (response_code == 200)
2506 {
2507 actx->errctx = "failed to parse access token response";
2508 if (!parse_access_token(actx, tok))
2509 return false; /* error message already set */
2510
2511 return true;
2512 }
2513
2514 /*
2515 * An error response uses either 400 Bad Request or 401 Unauthorized.
2516 * There are references online to implementations using 403 for error
2517 * return which would violate the specification. For now we stick to the
2518 * specification but we might have to revisit this.
2519 */
2520 if (response_code == 400 || response_code == 401)
2521 {
2522 if (!parse_token_error(actx, &tok->err))
2523 return false;
2524
2525 return true;
2526 }
2527
2528 /* Any other response codes are considered invalid */
2529 actx_error(actx, "unexpected response code %ld", response_code);
2530 return false;
2531}
static bool parse_access_token(struct async_ctx *actx, struct token *tok)
Definition: oauth-curl.c:1132
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 ( PGconnconn,
struct async_ctxactx 
)
static

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

289{
290 /*
291 * In general, none of the error cases below should ever happen if we have
292 * no bugs above. But if we do hit them, surfacing those errors somehow
293 * might be the only way to have a chance to debug them.
294 *
295 * TODO: At some point it'd be nice to have a standard way to warn about
296 * teardown failures. Appending to the connection's error message only
297 * helps if the bug caused a connection failure; otherwise it'll be
298 * buried...
299 */
300
301 if (actx->curlm && actx->curl)
302 {
303 CURLMcode err = curl_multi_remove_handle(actx->curlm, actx->curl);
304
305 if (err)
307 "libcurl easy handle removal failed: %s",
308 curl_multi_strerror(err));
309 }
310
311 if (actx->curl)
312 {
313 /*
314 * curl_multi_cleanup() doesn't free any associated easy handles; we
315 * need to do that separately. We only ever have one easy handle per
316 * multi handle.
317 */
318 curl_easy_cleanup(actx->curl);
319 }
320
321 if (actx->curlm)
322 {
323 CURLMcode err = curl_multi_cleanup(actx->curlm);
324
325 if (err)
327 "libcurl multi handle cleanup failed: %s",
328 curl_multi_strerror(err));
329 }
330
331 free_provider(&actx->provider);
332 free_device_authz(&actx->authz);
333
334 curl_slist_free_all(actx->headers);
336 termPQExpBuffer(&actx->errbuf);
337
338 if (actx->mux != PGINVALID_SOCKET)
339 close(actx->mux);
340 if (actx->timerfd >= 0)
341 close(actx->timerfd);
342
343 free(actx);
344}
#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
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_authzauthz )
static

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

152{
153 free(authz->device_code);
154 free(authz->user_code);
155 free(authz->verification_uri);
157 free(authz->expires_in_str);
158 free(authz->interval_str);
159}
char * interval_str
Definition: oauth-curl.c:143
char * user_code
Definition: oauth-curl.c:139
char * device_code
Definition: oauth-curl.c:138
char * expires_in_str
Definition: oauth-curl.c:142
char * verification_uri_complete
Definition: oauth-curl.c:141
char * verification_uri
Definition: oauth-curl.c:140

References device_authz::device_code, device_authz::expires_in_str, free, device_authz::interval_str, device_authz::user_code, device_authz::verification_uri, and device_authz::verification_uri_complete.

Referenced by free_async_ctx().

free_provider()

static void free_provider ( struct providerprovider )
static

free_token()

static void free_token ( struct tokentok )
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_errorerr )
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_ctxactx,
char **  token 
)
static

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

2544{
2545 bool success = false;
2546 struct token tok = {0};
2547 const struct token_error *err;
2548
2549 if (!finish_token_request(actx, &tok))
2550 goto token_cleanup;
2551
2552 /* A successful token request gives either a token or an in-band error. */
2553 Assert(tok.access_token || tok.err.error);
2554
2555 if (tok.access_token)
2556 {
2557 *token = tok.access_token;
2558 tok.access_token = NULL;
2559
2560 success = true;
2561 goto token_cleanup;
2562 }
2563
2564 /*
2565 * authorization_pending and slow_down are the only acceptable errors;
2566 * anything else and we bail. These are defined in RFC 8628, Sec. 3.5.
2567 */
2568 err = &tok.err;
2569 if (strcmp(err->error, "authorization_pending") != 0 &&
2570 strcmp(err->error, "slow_down") != 0)
2571 {
2572 record_token_error(actx, err);
2573 goto token_cleanup;
2574 }
2575
2576 /*
2577 * A slow_down error requires us to permanently increase our retry
2578 * interval by five seconds.
2579 */
2580 if (strcmp(err->error, "slow_down") == 0)
2581 {
2582 int prev_interval = actx->authz.interval;
2583
2584 actx->authz.interval += 5;
2585 if (actx->authz.interval < prev_interval)
2586 {
2587 actx_error(actx, "slow_down interval overflow");
2588 goto token_cleanup;
2589 }
2590 }
2591
2592 success = true;
2593
2594token_cleanup:
2595 free_token(&tok);
2596 return success;
2597}
static bool finish_token_request(struct async_ctx *actx, struct token *tok)
Definition: oauth-curl.c:2496
static void free_token(struct token *tok)
Definition: oauth-curl.c:202
int interval
Definition: oauth-curl.c:147
char * error
Definition: oauth-curl.c:171
Definition: oauth-curl.c:192

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 ( PGconnconn )
static

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

2650{
2651 /*
2652 * Don't let the compiler play tricks with this variable. In the
2653 * HAVE_THREADSAFE_CURL_GLOBAL_INIT case, we don't care if two threads
2654 * enter simultaneously, but we do care if this gets set transiently to
2655 * PG_BOOL_YES/NO in cases where that's not the final answer.
2656 */
2657 static volatile PGTernaryBool init_successful = PG_BOOL_UNKNOWN;
2658#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
2659 curl_version_info_data *info;
2660#endif
2661
2662#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2663
2664 /*
2665 * Lock around the whole function. If a libpq client performs its own work
2666 * with libcurl, it must either ensure that Curl is initialized safely
2667 * before calling us (in which case our call will be a no-op), or else it
2668 * must guard its own calls to curl_global_init() with a registered
2669 * threadlock handler. See PQregisterThreadLock().
2670 */
2671 pglock_thread();
2672#endif
2673
2674 /*
2675 * Skip initialization if we've already done it. (Curl tracks the number
2676 * of calls; there's no point in incrementing the counter every time we
2677 * connect.)
2678 */
2679 if (init_successful == PG_BOOL_YES)
2680 goto done;
2681 else if (init_successful == PG_BOOL_NO)
2682 {
2684 "curl_global_init previously failed during OAuth setup");
2685 goto done;
2686 }
2687
2688 /*
2689 * We know we've already initialized Winsock by this point (see
2690 * pqMakeEmptyPGconn()), so we should be able to safely skip that bit. But
2691 * we have to tell libcurl to initialize everything else, because other
2692 * pieces of our client executable may already be using libcurl for their
2693 * own purposes. If we initialize libcurl with only a subset of its
2694 * features, we could break those other clients nondeterministically, and
2695 * that would probably be a nightmare to debug.
2696 *
2697 * If some other part of the program has already called this, it's a
2698 * no-op.
2699 */
2700 if (curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32) != CURLE_OK)
2701 {
2703 "curl_global_init failed during OAuth setup");
2704 init_successful = PG_BOOL_NO;
2705 goto done;
2706 }
2707
2708#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
2709
2710 /*
2711 * If we determined at configure time that the Curl installation is
2712 * thread-safe, our job here is much easier. We simply initialize above
2713 * without any locking (concurrent or duplicated calls are fine in that
2714 * situation), then double-check to make sure the runtime setting agrees,
2715 * to try to catch silent downgrades.
2716 */
2717 info = curl_version_info(CURLVERSION_NOW);
2718 if (!(info->features & CURL_VERSION_THREADSAFE))
2719 {
2720 /*
2721 * In a downgrade situation, the damage is already done. Curl global
2722 * state may be corrupted. Be noisy.
2723 */
2724 libpq_append_conn_error(conn, "libcurl is no longer thread-safe\n"
2725 "\tCurl initialization was reported thread-safe when libpq\n"
2726 "\twas compiled, but the currently installed version of\n"
2727 "\tlibcurl reports that it is not. Recompile libpq against\n"
2728 "\tthe installed version of libcurl.");
2729 init_successful = PG_BOOL_NO;
2730 goto done;
2731 }
2732#endif
2733
2734 init_successful = PG_BOOL_YES;
2735
2736done:
2737#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2739#endif
2740 return (init_successful == PG_BOOL_YES);
2741}
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 633 of file oauth-curl.c.

634{
635 struct oauth_parse *ctx = state;
636
637 if (ctx->active)
638 {
639 /*
640 * Clear the target (which should be an array inside the top-level
641 * object). For this to be safe, no target arrays can contain other
642 * arrays; we check for that in the array_start callback.
643 */
644 if (ctx->nested != 2 || ctx->active->type != JSON_TOKEN_ARRAY_START)
645 {
646 Assert(false);
648 "internal error: found unexpected array end while parsing field '%s'",
649 ctx->active->name);
651 }
652
653 ctx->active = NULL;
654 }
655
656 --ctx->nested;
657 return JSON_SUCCESS;
658}
@ 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:461
const char * name
Definition: oauth-curl.c:433
JsonTokenType type
Definition: oauth-curl.c:435
const struct json_field * active
Definition: oauth-curl.c:458
int nested
Definition: oauth-curl.c:455
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 601 of file oauth-curl.c.

602{
603 struct oauth_parse *ctx = state;
604
605 if (!ctx->nested)
606 {
607 oauth_parse_set_error(ctx, "top-level element must be an object");
609 }
610
611 if (ctx->active)
612 {
614 /* The arrays we care about must not have arrays as values. */
615 || ctx->nested > 1)
616 {
619 }
620 }
621
622 ++ctx->nested;
624 {
625 oauth_parse_set_error(ctx, "JSON is too deeply nested");
627 }
628
629 return JSON_SUCCESS;
630}
static void report_type_mismatch(struct oauth_parse *ctx)
Definition: oauth-curl.c:465
#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 578 of file oauth-curl.c.

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

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

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

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

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

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

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

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

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_ctxactx,
struct device_authzauthz 
)
static

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

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

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_ctxactx,
const char *  expires_in_str 
)
static

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

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

References parse_json_number().

Referenced by parse_device_authz().

parse_interval()

static int parse_interval ( struct async_ctxactx,
const char *  interval_str 
)
static

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

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

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

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

References Assert().

Referenced by parse_expires_in(), and parse_interval().

parse_oauth_json()

static bool parse_oauth_json ( struct async_ctxactx,
const struct json_fieldfields 
)
static

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

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

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

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

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_ctxactx,
struct token_errorerr 
)
static

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

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

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 ( PGconnconn )

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

356{
358
359 if (state->async_ctx)
360 {
361 free_async_ctx(conn, state->async_ctx);
362 state->async_ctx = NULL;
363 }
364
366}
#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:288
#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 ( PGconnconn )

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

3023{
3026 struct async_ctx *actx;
3027#ifndef WIN32
3028 sigset_t osigset;
3029 bool sigpipe_pending;
3030 bool masked;
3031
3032 /*---
3033 * Ignore SIGPIPE on this thread during all Curl processing.
3034 *
3035 * Because we support multiple threads, we have to set up libcurl with
3036 * CURLOPT_NOSIGNAL, which disables its default global handling of
3037 * SIGPIPE. From the Curl docs:
3038 *
3039 * libcurl makes an effort to never cause such SIGPIPE signals to
3040 * trigger, but some operating systems have no way to avoid them and
3041 * even on those that have there are some corner cases when they may
3042 * still happen, contrary to our desire.
3043 *
3044 * Note that libcurl is also at the mercy of its DNS resolution and SSL
3045 * libraries; if any of them forget a MSG_NOSIGNAL then we're in trouble.
3046 * Modern platforms and libraries seem to get it right, so this is a
3047 * difficult corner case to exercise in practice, and unfortunately it's
3048 * not really clear whether it's necessary in all cases.
3049 */
3050 masked = (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0);
3051#endif
3052
3054
3055 /*
3056 * To assist with finding bugs in comb_multiplexer() and
3057 * drain_timer_events(), when we're in debug mode, track the total number
3058 * of calls to this function and print that at the end of the flow.
3059 *
3060 * Be careful that state->async_ctx could be NULL if early initialization
3061 * fails during the first call.
3062 */
3063 actx = state->async_ctx;
3064 Assert(actx || result == PGRES_POLLING_FAILED);
3065
3066 if (actx && actx->debugging)
3067 {
3068 actx->dbg_num_calls++;
3069 if (result == PGRES_POLLING_OK || result == PGRES_POLLING_FAILED)
3070 fprintf(stderr, "[libpq] total number of polls: %d\n",
3071 actx->dbg_num_calls);
3072 }
3073
3074#ifndef WIN32
3075 if (masked)
3076 {
3077 /*
3078 * Undo the SIGPIPE mask. Assume we may have gotten EPIPE (we have no
3079 * way of knowing at this level).
3080 */
3081 pq_reset_sigpipe(&osigset, sigpipe_pending, true /* EPIPE, maybe */ );
3082 }
3083#endif
3084
3085 return result;
3086}
PostgresPollingStatusType
Definition: libpq-fe.h:114
static PostgresPollingStatusType pg_fe_run_oauth_flow_impl(PGconn *conn)
Definition: oauth-curl.c:2758
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
int dbg_num_calls
Definition: oauth-curl.c:281

References Assert(), conn, conn_sasl_state, async_ctx::dbg_num_calls, async_ctx::debugging, fprintf, pg_fe_run_oauth_flow_impl(), PGRES_POLLING_FAILED, PGRES_POLLING_OK, pq_block_sigpipe(), and pq_reset_sigpipe().

pg_fe_run_oauth_flow_impl()

static PostgresPollingStatusType pg_fe_run_oauth_flow_impl ( PGconnconn )
static

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

2759{
2761 struct async_ctx *actx;
2762 char *oauth_token = NULL;
2764
2765 if (!initialize_curl(conn))
2766 return PGRES_POLLING_FAILED;
2767
2768 if (!state->async_ctx)
2769 {
2770 /*
2771 * Create our asynchronous state, and hook it into the upper-level
2772 * OAuth state immediately, so any failures below won't leak the
2773 * context allocation.
2774 */
2775 actx = calloc(1, sizeof(*actx));
2776 if (!actx)
2777 {
2778 libpq_append_conn_error(conn, "out of memory");
2779 return PGRES_POLLING_FAILED;
2780 }
2781
2782 actx->mux = PGINVALID_SOCKET;
2783 actx->timerfd = -1;
2784
2785 /* Should we enable unsafe features? */
2787
2788 state->async_ctx = actx;
2789
2790 initPQExpBuffer(&actx->work_data);
2791 initPQExpBuffer(&actx->errbuf);
2792
2793 if (!setup_multiplexer(actx))
2794 goto error_return;
2795
2796 if (!setup_curl_handles(actx))
2797 goto error_return;
2798 }
2799
2800 actx = state->async_ctx;
2801
2802 do
2803 {
2804 /* By default, the multiplexer is the altsock. Reassign as desired. */
2805 set_conn_altsock(conn, actx->mux);
2806
2807 switch (actx->step)
2808 {
2809 case OAUTH_STEP_INIT:
2810 break;
2811
2815 {
2817
2818 /*
2819 * Clear any expired timeout before calling back into
2820 * Curl. Curl is not guaranteed to do this for us, because
2821 * its API expects us to use single-shot (i.e.
2822 * edge-triggered) timeouts, and ours are level-triggered
2823 * via the mux.
2824 *
2825 * This can't be combined with the comb_multiplexer() call
2826 * below: we might accidentally clear a short timeout that
2827 * was both set and expired during the call to
2828 * drive_request().
2829 */
2830 if (!drain_timer_events(actx, NULL))
2831 goto error_return;
2832
2833 /* Move the request forward. */
2834 status = drive_request(actx);
2835
2836 if (status == PGRES_POLLING_FAILED)
2837 goto error_return;
2838 else if (status == PGRES_POLLING_OK)
2839 break; /* done! */
2840
2841 /*
2842 * This request is still running.
2843 *
2844 * Make sure that stale events don't cause us to come back
2845 * early. (Currently, this can occur only with kqueue.) If
2846 * this is forgotten, the multiplexer can get stuck in a
2847 * signaled state and we'll burn CPU cycles pointlessly.
2848 */
2849 if (!comb_multiplexer(actx))
2850 goto error_return;
2851
2852 return status;
2853 }
2854
2856 {
2857 bool expired;
2858
2859 /*
2860 * The client application is supposed to wait until our
2861 * timer expires before calling PQconnectPoll() again, but
2862 * that might not happen. To avoid sending a token request
2863 * early, check the timer before continuing.
2864 */
2865 if (!drain_timer_events(actx, &expired))
2866 goto error_return;
2867
2868 if (!expired)
2869 {
2871 return PGRES_POLLING_READING;
2872 }
2873
2874 break;
2875 }
2876 }
2877
2878 /*
2879 * Each case here must ensure that actx->running is set while we're
2880 * waiting on some asynchronous work. Most cases rely on
2881 * start_request() to do that for them.
2882 */
2883 switch (actx->step)
2884 {
2885 case OAUTH_STEP_INIT:
2886 actx->errctx = "failed to fetch OpenID discovery document";
2888 goto error_return;
2889
2890 actx->step = OAUTH_STEP_DISCOVERY;
2891 break;
2892
2894 if (!finish_discovery(actx))
2895 goto error_return;
2896
2897 if (!check_issuer(actx, conn))
2898 goto error_return;
2899
2900 actx->errctx = "cannot run OAuth device authorization";
2901 if (!check_for_device_flow(actx))
2902 goto error_return;
2903
2904 actx->errctx = "failed to obtain device authorization";
2905 if (!start_device_authz(actx, conn))
2906 goto error_return;
2907
2909 break;
2910
2912 if (!finish_device_authz(actx))
2913 goto error_return;
2914
2915 actx->errctx = "failed to obtain access token";
2916 if (!start_token_request(actx, conn))
2917 goto error_return;
2918
2920 break;
2921
2923 if (!handle_token_response(actx, &oauth_token))
2924 goto error_return;
2925
2926 /*
2927 * Hook any oauth_token into the PGconn immediately so that
2928 * the allocation isn't lost in case of an error.
2929 */
2930 set_conn_oauth_token(conn, oauth_token);
2931
2932 if (!actx->user_prompted)
2933 {
2934 /*
2935 * Now that we know the token endpoint isn't broken, give
2936 * the user the login instructions.
2937 */
2938 if (!prompt_user(actx, conn))
2939 goto error_return;
2940
2941 actx->user_prompted = true;
2942 }
2943
2944 if (oauth_token)
2945 break; /* done! */
2946
2947 /*
2948 * Wait for the required interval before issuing the next
2949 * request.
2950 */
2951 if (!set_timer(actx, actx->authz.interval * 1000))
2952 goto error_return;
2953
2954 /*
2955 * No Curl requests are running, so we can simplify by having
2956 * the client wait directly on the timerfd rather than the
2957 * multiplexer.
2958 */
2960
2962 actx->running = 1;
2963 break;
2964
2966 actx->errctx = "failed to obtain access token";
2967 if (!start_token_request(actx, conn))
2968 goto error_return;
2969
2971 break;
2972 }
2973
2974 /*
2975 * The vast majority of the time, if we don't have a token at this
2976 * point, actx->running will be set. But there are some corner cases
2977 * where we can immediately loop back around; see start_request().
2978 */
2979 } while (!oauth_token && !actx->running);
2980
2981 /* If we've stored a token, we're done. Otherwise come back later. */
2982 return oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
2983
2984error_return:
2986
2987 /*
2988 * Assemble the three parts of our error: context, body, and detail. See
2989 * also the documentation for struct async_ctx.
2990 */
2991 if (actx->errctx)
2993
2994 if (PQExpBufferDataBroken(actx->errbuf))
2995 appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
2996 else
2998
2999 if (actx->curl_err[0])
3000 {
3001 appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
3002
3003 /* Sometimes libcurl adds a newline to the error buffer. :( */
3004 if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
3005 {
3006 errbuf->data[errbuf->len - 2] = ')';
3007 errbuf->data[errbuf->len - 1] = '0円';
3008 errbuf->len--;
3009 }
3010 }
3011
3013
3014 return PGRES_POLLING_FAILED;
3015}
#define calloc(a, b)
Definition: header.h:55
static bool drain_timer_events(struct async_ctx *actx, bool *was_expired)
Definition: oauth-curl.c:1590
static bool setup_multiplexer(struct async_ctx *actx)
Definition: oauth-curl.c:1174
static bool start_token_request(struct async_ctx *actx, PGconn *conn)
Definition: oauth-curl.c:2464
static bool initialize_curl(PGconn *conn)
Definition: oauth-curl.c:2649
static PostgresPollingStatusType drive_request(struct async_ctx *actx)
Definition: oauth-curl.c:1927
static bool start_device_authz(struct async_ctx *actx, PGconn *conn)
Definition: oauth-curl.c:2376
static bool prompt_user(struct async_ctx *actx, PGconn *conn)
Definition: oauth-curl.c:2604
static bool finish_discovery(struct async_ctx *actx)
Definition: oauth-curl.c:2123
#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:2114
static bool finish_device_authz(struct async_ctx *actx)
Definition: oauth-curl.c:2407
static bool comb_multiplexer(struct async_ctx *actx)
Definition: oauth-curl.c:1399
#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:2188
#define conn_errorMessage(CONN)
Definition: oauth-curl.c:53
static bool handle_token_response(struct async_ctx *actx, char **token)
Definition: oauth-curl.c:2543
static bool check_for_device_flow(struct async_ctx *actx)
Definition: oauth-curl.c:2233
static bool setup_curl_handles(struct async_ctx *actx)
Definition: oauth-curl.c:1705
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(), comb_multiplexer(), conn, conn_errorMessage, conn_oauth_discovery_uri, conn_sasl_state, async_ctx::curl_err, PQExpBufferData::data, async_ctx::debugging, drain_timer_events(), 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, 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_ctxactx,
PGconnconn 
)
static

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

2605{
2606 int res;
2607 PGpromptOAuthDevice prompt = {
2609 .user_code = actx->authz.user_code,
2610 .verification_uri_complete = actx->authz.verification_uri_complete,
2611 .expires_in = actx->authz.expires_in,
2612 };
2614
2615 res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
2616
2617 if (!res)
2618 {
2619 /*
2620 * translator: The first %s is a URL for the user to visit in a
2621 * browser, and the second %s is a code to be copy-pasted there.
2622 */
2623 fprintf(stderr, libpq_gettext("Visit %s and enter the code: %s\n"),
2624 prompt.verification_uri, prompt.user_code);
2625 }
2626 else if (res < 0)
2627 {
2628 actx_error(actx, "device prompt failed");
2629 return false;
2630 }
2631
2632 return true;
2633}
PQauthDataHook_type PQgetAuthDataHook(void)
Definition: fe-auth.c:1589
int(* PQauthDataHook_type)(PGauthData type, PGconn *conn, void *data)
Definition: libpq-fe.h:807
@ PQAUTHDATA_PROMPT_OAUTH_DEVICE
Definition: libpq-fe.h:194
const char * verification_uri
Definition: libpq-fe.h:734
const char * user_code
Definition: libpq-fe.h:735

References actx_error, async_ctx::authz, conn, device_authz::expires_in, fprintf, libpq_gettext, PQAUTHDATA_PROMPT_OAUTH_DEVICE, 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_ctxactx,
const struct token_errorerr 
)
static

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

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

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

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

1569{
1570 struct async_ctx *actx = ctx;
1571
1572 /*
1573 * There might be an optimization opportunity here: if timeout == 0, we
1574 * could signal drive_request to immediately call
1575 * curl_multi_socket_action, rather than returning all the way up the
1576 * stack only to come right back. But it's not clear that the additional
1577 * code complexity is worth it.
1578 */
1579 if (!set_timer(actx, timeout))
1580 return -1; /* actx_error already called */
1581
1582 return 0;
1583}

References set_timer().

Referenced by setup_curl_handles().

report_type_mismatch()

static void report_type_mismatch ( struct oauth_parsectx )
static

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

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

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_ctxactx,
long  timeout 
)
static

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

1448{
1449#if defined(HAVE_SYS_EPOLL_H)
1450 struct itimerspec spec = {0};
1451
1452 if (timeout < 0)
1453 {
1454 /* the zero itimerspec will disarm the timer below */
1455 }
1456 else if (timeout == 0)
1457 {
1458 /*
1459 * A zero timeout means libcurl wants us to call back immediately.
1460 * That's not technically an option for timerfd, but we can make the
1461 * timeout ridiculously short.
1462 */
1463 spec.it_value.tv_nsec = 1;
1464 }
1465 else
1466 {
1467 spec.it_value.tv_sec = timeout / 1000;
1468 spec.it_value.tv_nsec = (timeout % 1000) * 1000000;
1469 }
1470
1471 if (timerfd_settime(actx->timerfd, 0 /* no flags */ , &spec, NULL) < 0)
1472 {
1473 actx_error(actx, "setting timerfd to %ld: %m", timeout);
1474 return false;
1475 }
1476
1477 return true;
1478#elif defined(HAVE_SYS_EVENT_H)
1479 struct kevent ev;
1480
1481#ifdef __NetBSD__
1482
1483 /*
1484 * Work around NetBSD's rejection of zero timeouts (EINVAL), a bit like
1485 * timerfd above.
1486 */
1487 if (timeout == 0)
1488 timeout = 1;
1489#endif
1490
1491 /*
1492 * Always disable the timer, and remove it from the multiplexer, to clear
1493 * out any already-queued events. (On some BSDs, adding an EVFILT_TIMER to
1494 * a kqueue that already has one will clear stale events, but not on
1495 * macOS.)
1496 *
1497 * If there was no previous timer set, the kevent calls will result in
1498 * ENOENT, which is fine.
1499 */
1500 EV_SET(&ev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, 0);
1501 if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1502 {
1503 actx_error(actx, "deleting kqueue timer: %m");
1504 return false;
1505 }
1506
1507 EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_DELETE, 0, 0, 0);
1508 if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1509 {
1510 actx_error(actx, "removing kqueue timer from multiplexer: %m");
1511 return false;
1512 }
1513
1514 /* If we're not adding a timer, we're done. */
1515 if (timeout < 0)
1516 return true;
1517
1518 EV_SET(&ev, 1, EVFILT_TIMER, (EV_ADD | EV_ONESHOT), 0, timeout, 0);
1519 if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0)
1520 {
1521 actx_error(actx, "setting kqueue timer to %ld: %m", timeout);
1522 return false;
1523 }
1524
1525 EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_ADD, 0, 0, 0);
1526 if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0)
1527 {
1528 actx_error(actx, "adding kqueue timer to multiplexer: %m");
1529 return false;
1530 }
1531
1532 return true;
1533#else
1534#error set_timer is not implemented on this platform
1535#endif
1536}

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

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

setup_curl_handles()

static bool setup_curl_handles ( struct async_ctxactx )
static

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

1706{
1707 /*
1708 * Create our multi handle. This encapsulates the entire conversation with
1709 * libcurl for this connection.
1710 */
1711 actx->curlm = curl_multi_init();
1712 if (!actx->curlm)
1713 {
1714 /* We don't get a lot of feedback on the failure reason. */
1715 actx_error(actx, "failed to create libcurl multi handle");
1716 return false;
1717 }
1718
1719 /*
1720 * The multi handle tells us what to wait on using two callbacks. These
1721 * will manipulate actx->mux as needed.
1722 */
1723 CHECK_MSETOPT(actx, CURLMOPT_SOCKETFUNCTION, register_socket, return false);
1724 CHECK_MSETOPT(actx, CURLMOPT_SOCKETDATA, actx, return false);
1725 CHECK_MSETOPT(actx, CURLMOPT_TIMERFUNCTION, register_timer, return false);
1726 CHECK_MSETOPT(actx, CURLMOPT_TIMERDATA, actx, return false);
1727
1728 /*
1729 * Set up an easy handle. All of our requests are made serially, so we
1730 * only ever need to keep track of one.
1731 */
1732 actx->curl = curl_easy_init();
1733 if (!actx->curl)
1734 {
1735 actx_error(actx, "failed to create libcurl handle");
1736 return false;
1737 }
1738
1739 /*
1740 * Multi-threaded applications must set CURLOPT_NOSIGNAL. This requires us
1741 * to handle the possibility of SIGPIPE ourselves using pq_block_sigpipe;
1742 * see pg_fe_run_oauth_flow().
1743 *
1744 * NB: If libcurl is not built against a friendly DNS resolver (c-ares or
1745 * threaded), setting this option prevents DNS lookups from timing out
1746 * correctly. We warn about this situation at configure time.
1747 *
1748 * TODO: Perhaps there's a clever way to warn the user about synchronous
1749 * DNS at runtime too? It's not immediately clear how to do that in a
1750 * helpful way: for many standard single-threaded use cases, the user
1751 * might not care at all, so spraying warnings to stderr would probably do
1752 * more harm than good.
1753 */
1754 CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L, return false);
1755
1756 if (actx->debugging)
1757 {
1758 /*
1759 * Set a callback for retrieving error information from libcurl, the
1760 * function only takes effect when CURLOPT_VERBOSE has been set so
1761 * make sure the order is kept.
1762 */
1763 CHECK_SETOPT(actx, CURLOPT_DEBUGFUNCTION, debug_callback, return false);
1764 CHECK_SETOPT(actx, CURLOPT_VERBOSE, 1L, return false);
1765 }
1766
1767 CHECK_SETOPT(actx, CURLOPT_ERRORBUFFER, actx->curl_err, return false);
1768
1769 /*
1770 * Only HTTPS is allowed. (Debug mode additionally allows HTTP; this is
1771 * intended for testing only.)
1772 *
1773 * There's a bit of unfortunate complexity around the choice of
1774 * CURLoption. CURLOPT_PROTOCOLS is deprecated in modern Curls, but its
1775 * replacement didn't show up until relatively recently.
1776 */
1777 {
1778#if CURL_AT_LEAST_VERSION(7, 85, 0)
1779 const CURLoption popt = CURLOPT_PROTOCOLS_STR;
1780 const char *protos = "https";
1781 const char *const unsafe = "https,http";
1782#else
1783 const CURLoption popt = CURLOPT_PROTOCOLS;
1784 long protos = CURLPROTO_HTTPS;
1785 const long unsafe = CURLPROTO_HTTPS | CURLPROTO_HTTP;
1786#endif
1787
1788 if (actx->debugging)
1789 protos = unsafe;
1790
1791 CHECK_SETOPT(actx, popt, protos, return false);
1792 }
1793
1794 /*
1795 * If we're in debug mode, allow the developer to change the trusted CA
1796 * list. For now, this is not something we expose outside of the UNSAFE
1797 * mode, because it's not clear that it's useful in production: both libpq
1798 * and the user's browser must trust the same authorization servers for
1799 * the flow to work at all, so any changes to the roots are likely to be
1800 * done system-wide.
1801 */
1802 if (actx->debugging)
1803 {
1804 const char *env;
1805
1806 if ((env = getenv("PGOAUTHCAFILE")) != NULL)
1807 CHECK_SETOPT(actx, CURLOPT_CAINFO, env, return false);
1808 }
1809
1810 /*
1811 * Suppress the Accept header to make our request as minimal as possible.
1812 * (Ideally we would set it to "application/json" instead, but OpenID is
1813 * pretty strict when it comes to provider behavior, so we have to check
1814 * what comes back anyway.)
1815 */
1816 actx->headers = curl_slist_append(actx->headers, "Accept:");
1817 if (actx->headers == NULL)
1818 {
1819 actx_error(actx, "out of memory");
1820 return false;
1821 }
1822 CHECK_SETOPT(actx, CURLOPT_HTTPHEADER, actx->headers, return false);
1823
1824 return true;
1825}
static int register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx, void *socketp)
Definition: oauth-curl.c:1233
#define CHECK_MSETOPT(ACTX, OPT, VAL, FAILACTION)
Definition: oauth-curl.c:385
static int debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *clientp)
Definition: oauth-curl.c:1622
static int register_timer(CURLM *curlm, long timeout, void *ctx)
Definition: oauth-curl.c:1568

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_ctxactx )
static

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

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

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_ctxactx,
PGconnconn 
)
static

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

2377{
2378 const char *oauth_scope = conn_oauth_scope(conn);
2379 const char *device_authz_uri = actx->provider.device_authorization_endpoint;
2380 PQExpBuffer work_buffer = &actx->work_data;
2381
2382 Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
2383 Assert(device_authz_uri); /* ensured by check_for_device_flow() */
2384
2385 /* Construct our request body. */
2386 resetPQExpBuffer(work_buffer);
2387 if (oauth_scope && oauth_scope[0])
2388 build_urlencoded(work_buffer, "scope", oauth_scope);
2389
2390 if (!add_client_identification(actx, work_buffer, conn))
2391 return false;
2392
2393 if (PQExpBufferBroken(work_buffer))
2394 {
2395 actx_error(actx, "out of memory");
2396 return false;
2397 }
2398
2399 /* Make our request. */
2400 CHECK_SETOPT(actx, CURLOPT_URL, device_authz_uri, return false);
2401 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2402
2403 return start_request(actx);
2404}
static bool add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
Definition: oauth-curl.c:2292
#define conn_oauth_scope(CONN)
Definition: oauth-curl.c:58
static bool start_request(struct async_ctx *actx)
Definition: oauth-curl.c:1878
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_ctxactx,
const char *  discovery_uri 
)
static

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

2115{
2116 CHECK_SETOPT(actx, CURLOPT_HTTPGET, 1L, return false);
2117 CHECK_SETOPT(actx, CURLOPT_URL, discovery_uri, return false);
2118
2119 return start_request(actx);
2120}

References CHECK_SETOPT, and start_request().

Referenced by pg_fe_run_oauth_flow_impl().

start_request()

static bool start_request ( struct async_ctxactx )
static

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

1879{
1880 CURLMcode err;
1881
1883 CHECK_SETOPT(actx, CURLOPT_WRITEFUNCTION, append_data, return false);
1884 CHECK_SETOPT(actx, CURLOPT_WRITEDATA, actx, return false);
1885
1886 err = curl_multi_add_handle(actx->curlm, actx->curl);
1887 if (err)
1888 {
1889 actx_error(actx, "failed to queue HTTP request: %s",
1890 curl_multi_strerror(err));
1891 return false;
1892 }
1893
1894 /*
1895 * actx->running tracks the number of running handles, so we can
1896 * immediately call back if no waiting is needed.
1897 *
1898 * Even though this is nominally an asynchronous process, there are some
1899 * operations that can synchronously fail by this point (e.g. connections
1900 * to closed local ports) or even synchronously succeed if the stars align
1901 * (all the libcurl connection caches hit and the server is fast).
1902 */
1903 err = curl_multi_socket_action(actx->curlm, CURL_SOCKET_TIMEOUT, 0, &actx->running);
1904 if (err)
1905 {
1906 actx_error(actx, "asynchronous HTTP request failed: %s",
1907 curl_multi_strerror(err));
1908 return false;
1909 }
1910
1911 return true;
1912}
static size_t append_data(char *buf, size_t size, size_t nmemb, void *userdata)
Definition: oauth-curl.c:1838

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_ctxactx,
PGconnconn 
)
static

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

2465{
2466 const char *token_uri = actx->provider.token_endpoint;
2467 const char *device_code = actx->authz.device_code;
2468 PQExpBuffer work_buffer = &actx->work_data;
2469
2470 Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
2471 Assert(token_uri); /* ensured by parse_provider() */
2472 Assert(device_code); /* ensured by parse_device_authz() */
2473
2474 /* Construct our request body. */
2475 resetPQExpBuffer(work_buffer);
2476 build_urlencoded(work_buffer, "device_code", device_code);
2477 build_urlencoded(work_buffer, "grant_type", OAUTH_GRANT_TYPE_DEVICE_CODE);
2478
2479 if (!add_client_identification(actx, work_buffer, conn))
2480 return false;
2481
2482 if (PQExpBufferBroken(work_buffer))
2483 {
2484 actx_error(actx, "out of memory");
2485 return false;
2486 }
2487
2488 /* Make our request. */
2489 CHECK_SETOPT(actx, CURLOPT_URL, token_uri, return false);
2490 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2491
2492 return start_request(actx);
2493}
#define OAUTH_GRANT_TYPE_DEVICE_CODE
Definition: oauth-curl.c:2225

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_ctxactx )
static

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

1545{
1546#if defined(HAVE_SYS_EPOLL_H) || defined(HAVE_SYS_EVENT_H)
1547 int res;
1548
1549 /* Is the timer ready? */
1550 res = PQsocketPoll(actx->timerfd, 1 /* forRead */ , 0, 0);
1551 if (res < 0)
1552 {
1553 actx_error(actx, "checking timer expiration: %m");
1554 return -1;
1555 }
1556
1557 return (res > 0);
1558#else
1559#error timer_expired is not implemented on this platform
1560#endif
1561}
int PQsocketPoll(int sock, int forRead, int forWrite, pg_usec_time_t end_time)
Definition: fe-misc.c:1141

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

Referenced by drain_timer_events().

urlencode()

static char * urlencode ( const char *  s )
static

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

2071{
2073
2076
2077 return PQExpBufferDataBroken(buf) ? NULL : buf.data;
2078}

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

Referenced by add_client_identification().

AltStyle によって変換されたページ (->オリジナル) /