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