PostgreSQL Source Code git master
oauth-curl.c
Go to the documentation of this file.
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 */
115{
116 char *issuer;
119 struct curl_slist *grant_types_supported;
120};
121
122static void
124{
128 curl_slist_free_all(provider->grant_types_supported);
129}
130
131/*
132 * The Device Authorization response, described by RFC 8628:
133 *
134 * https://www.rfc-editor.org/rfc/rfc8628#section-3.2
135 */
137{
144
145 /* Fields below are parsed from the corresponding string above. */
148};
149
150static void
152{
153 free(authz->device_code);
154 free(authz->user_code);
155 free(authz->verification_uri);
157 free(authz->expires_in_str);
158 free(authz->interval_str);
159}
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 */
170{
171 char *error;
173};
174
175static void
177{
178 free(err->error);
179 free(err->error_description);
180}
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 */
191struct token
192{
193 /* for successful responses */
196
197 /* for error responses */
199};
200
201static void
202free_token(struct token *tok)
203{
204 free(tok->access_token);
205 free(tok->token_type);
206 free_token_error(&tok->err);
207}
208
209/*
210 * Asynchronous State
211 */
212
213/* States for the overall async machine. */
215{
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 */
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 */
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 */
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 */
288static void
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 if (actx->curlm && actx->curl)
303 {
304 CURLMcode err = curl_multi_remove_handle(actx->curlm, actx->curl);
305
306 if (err)
308 "libcurl easy handle removal failed: %s",
309 curl_multi_strerror(err));
310 }
311
312 if (actx->curl)
313 {
314 /*
315 * curl_multi_cleanup() doesn't free any associated easy handles; we
316 * need to do that separately. We only ever have one easy handle per
317 * multi handle.
318 */
319 curl_easy_cleanup(actx->curl);
320 }
321
322 if (actx->curlm)
323 {
324 CURLMcode err = curl_multi_cleanup(actx->curlm);
325
326 if (err)
328 "libcurl multi handle cleanup failed: %s",
329 curl_multi_strerror(err));
330 }
331
332 free_provider(&actx->provider);
333 free_device_authz(&actx->authz);
334
335 curl_slist_free_all(actx->headers);
337 termPQExpBuffer(&actx->errbuf);
338
339 if (actx->mux != PGINVALID_SOCKET)
340 close(actx->mux);
341 if (actx->timerfd >= 0)
342 close(actx->timerfd);
343
344 free(actx);
345}
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 */
355void
357{
359
360 if (state->async_ctx)
361 {
362 free_async_ctx(conn, state->async_ctx);
363 state->async_ctx = NULL;
364 }
365
367}
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 */
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(). */
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
471static void
473{
474 char *msgfmt;
475
476 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 switch (ctx->active->type)
483 {
485 msgfmt = gettext_noop("field \"%s\" must be a string");
486 break;
487
489 msgfmt = gettext_noop("field \"%s\" must be a number");
490 break;
491
493 msgfmt = gettext_noop("field \"%s\" must be an array of strings");
494 break;
495
496 default:
497 Assert(false);
498 msgfmt = gettext_noop("field \"%s\" has unexpected type");
499 }
500
501 oauth_parse_set_error(ctx, msgfmt, ctx->active->name);
502}
503
506{
507 struct oauth_parse *ctx = state;
508
509 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 */
517 }
518
519 ++ctx->nested;
521 {
522 oauth_parse_set_error(ctx, "JSON is too deeply nested");
524 }
525
526 return JSON_SUCCESS;
527}
528
530oauth_json_object_field_start(void *state, char *name, bool isnull)
531{
532 struct oauth_parse *ctx = state;
533
534 /* We care only about the top-level fields. */
535 if (ctx->nested == 1)
536 {
537 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 if (ctx->active)
544 {
545 Assert(false);
547 "internal error: started field \"%s\" before field \"%s\" was finished",
548 name, ctx->active->name);
550 }
551
552 while (field->name)
553 {
554 if (strcmp(name, field->name) == 0)
555 {
556 ctx->active = field;
557 break;
558 }
559
560 ++field;
561 }
562
563 /*
564 * We don't allow duplicate field names; error out if the target has
565 * already been set.
566 */
567 if (ctx->active)
568 {
569 field = ctx->active;
570
571 if ((field->type == JSON_TOKEN_ARRAY_START && *field->array)
572 || (field->type != JSON_TOKEN_ARRAY_START && *field->scalar))
573 {
574 oauth_parse_set_error(ctx, "field \"%s\" is duplicated",
575 field->name);
577 }
578 }
579 }
580
581 return JSON_SUCCESS;
582}
583
586{
587 struct oauth_parse *ctx = state;
588
589 --ctx->nested;
590
591 /*
592 * All fields should be fully processed by the end of the top-level
593 * object.
594 */
595 if (!ctx->nested && ctx->active)
596 {
597 Assert(false);
599 "internal error: field \"%s\" still active at end of object",
600 ctx->active->name);
602 }
603
604 return JSON_SUCCESS;
605}
606
609{
610 struct oauth_parse *ctx = state;
611
612 if (!ctx->nested)
613 {
614 oauth_parse_set_error(ctx, "top-level element must be an object");
616 }
617
618 if (ctx->active)
619 {
621 /* The arrays we care about must not have arrays as values. */
622 || ctx->nested > 1)
623 {
626 }
627 }
628
629 ++ctx->nested;
631 {
632 oauth_parse_set_error(ctx, "JSON is too deeply nested");
634 }
635
636 return JSON_SUCCESS;
637}
638
641{
642 struct oauth_parse *ctx = state;
643
644 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 if (ctx->nested != 2 || ctx->active->type != JSON_TOKEN_ARRAY_START)
652 {
653 Assert(false);
655 "internal error: found unexpected array end while parsing field \"%s\"",
656 ctx->active->name);
658 }
659
660 ctx->active = NULL;
661 }
662
663 --ctx->nested;
664 return JSON_SUCCESS;
665}
666
669{
670 struct oauth_parse *ctx = state;
671
672 if (!ctx->nested)
673 {
674 oauth_parse_set_error(ctx, "top-level element must be an object");
676 }
677
678 if (ctx->active)
679 {
680 const struct json_field *field = ctx->active;
681 JsonTokenType expected = field->type;
682
683 /* Make sure this matches what the active field expects. */
684 if (expected == JSON_TOKEN_ARRAY_START)
685 {
686 /* Are we actually inside an array? */
687 if (ctx->nested < 2)
688 {
691 }
692
693 /* Currently, arrays can only contain strings. */
694 expected = JSON_TOKEN_STRING;
695 }
696
697 if (type != expected)
698 {
701 }
702
703 if (field->type != JSON_TOKEN_ARRAY_START)
704 {
705 /* Ensure that we're parsing the top-level keys... */
706 if (ctx->nested != 1)
707 {
708 Assert(false);
710 "internal error: scalar target found at nesting level %d",
711 ctx->nested);
713 }
714
715 /* ...and that a result has not already been set. */
716 if (*field->scalar)
717 {
718 Assert(false);
720 "internal error: scalar field \"%s\" would be assigned twice",
721 ctx->active->name);
723 }
724
725 *field->scalar = strdup(token);
726 if (!*field->scalar)
727 return JSON_OUT_OF_MEMORY;
728
729 ctx->active = NULL;
730
731 return JSON_SUCCESS;
732 }
733 else
734 {
735 struct curl_slist *temp;
736
737 /* The target array should be inside the top-level object. */
738 if (ctx->nested != 2)
739 {
740 Assert(false);
742 "internal error: array member found at nesting level %d",
743 ctx->nested);
745 }
746
747 /* Note that curl_slist_append() makes a copy of the token. */
748 temp = curl_slist_append(*field->array, token);
749 if (!temp)
750 return JSON_OUT_OF_MEMORY;
751
752 *field->array = temp;
753 }
754 }
755 else
756 {
757 /* otherwise we just ignore it */
758 }
759
760 return JSON_SUCCESS;
761}
762
763/*
764 * Checks the Content-Type header against the expected type. Parameters are
765 * allowed but ignored.
766 */
767static bool
768check_content_type(struct async_ctx *actx, const char *type)
769{
770 const size_t type_len = strlen(type);
771 char *content_type;
772
773 CHECK_GETINFO(actx, CURLINFO_CONTENT_TYPE, &content_type, return false);
774
775 if (!content_type)
776 {
777 actx_error(actx, "no content type was provided");
778 return false;
779 }
780
781 /*
782 * We need to perform a length limited comparison and not compare the
783 * whole string.
784 */
785 if (pg_strncasecmp(content_type, type, type_len) != 0)
786 goto fail;
787
788 /* On an exact match, we're done. */
789 Assert(strlen(content_type) >= type_len);
790 if (content_type[type_len] == '\0')
791 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 for (size_t i = type_len; content_type[i]; ++i)
799 {
800 switch (content_type[i])
801 {
802 case ';':
803 return true; /* success! */
804
805 case ' ':
806 case '\t':
807 /* HTTP optional whitespace allows only spaces and htabs. */
808 break;
809
810 default:
811 goto fail;
812 }
813 }
814
815fail:
816 actx_error(actx, "unexpected content type: \"%s\"", content_type);
817 return false;
818}
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 */
826static bool
827parse_oauth_json(struct async_ctx *actx, const struct json_field *fields)
828{
829 PQExpBuffer resp = &actx->work_data;
830 JsonLexContext lex = {0};
831 JsonSemAction sem = {0};
833 struct oauth_parse ctx = {0};
834 bool success = false;
835
836 if (!check_content_type(actx, "application/json"))
837 return false;
838
839 if (strlen(resp->data) != resp->len)
840 {
841 actx_error(actx, "response contains embedded NULLs");
842 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 if (pg_encoding_verifymbstr(PG_UTF8, resp->data, resp->len) != resp->len)
850 {
851 actx_error(actx, "response is not valid UTF-8");
852 return false;
853 }
854
855 makeJsonLexContextCstringLen(&lex, resp->data, resp->len, PG_UTF8, true);
856 setJsonLexContextOwnsTokens(&lex, true); /* must not leak on error */
857
858 ctx.errbuf = &actx->errbuf;
859 ctx.fields = fields;
860 sem.semstate = &ctx;
861
868
869 err = pg_parse_json(&lex, &sem);
870
871 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 */
879 actx_error_str(actx, json_errdetail(err, &lex));
880
881 goto cleanup;
882 }
883
884 /* Check all required fields. */
885 while (fields->name)
886 {
887 if (fields->required
888 && !*fields->scalar
889 && !*fields->array)
890 {
891 actx_error(actx, "field \"%s\" is missing", fields->name);
892 goto cleanup;
893 }
894
895 fields++;
896 }
897
898 success = true;
899
900cleanup:
901 freeJsonLexContext(&lex);
902 return success;
903}
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 */
913static bool
915{
916 struct json_field fields[] = {
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 {"device_authorization_endpoint", JSON_TOKEN_STRING, {&provider->device_authorization_endpoint}, PG_OAUTH_OPTIONAL},
931 {"grant_types_supported", JSON_TOKEN_ARRAY_START, {.array = &provider->grant_types_supported}, PG_OAUTH_OPTIONAL},
932
933 {0},
934 };
935
936 return parse_oauth_json(actx, fields);
937}
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 */
944static double
945parse_json_number(const char *s)
946{
947 double parsed;
948 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 cnt = sscanf(s, "%lf", &parsed);
955
956 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 Assert(false);
963 return 0;
964 }
965
966 return parsed;
967}
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 */
979static int
980parse_interval(struct async_ctx *actx, const char *interval_str)
981{
982 double parsed;
983
984 parsed = parse_json_number(interval_str);
985 parsed = ceil(parsed);
986
987 if (parsed < 1)
988 return actx->debugging ? 0 : 1;
989
990 else if (parsed >= INT_MAX)
991 return INT_MAX;
992
993 return parsed;
994}
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 */
1005static int
1006parse_expires_in(struct async_ctx *actx, const char *expires_in_str)
1007{
1008 double parsed;
1009
1010 parsed = parse_json_number(expires_in_str);
1011 parsed = floor(parsed);
1012
1013 if (parsed >= INT_MAX)
1014 return INT_MAX;
1015 else if (parsed <= INT_MIN)
1016 return INT_MIN;
1017
1018 return parsed;
1019}
1020
1021/*
1022 * Parses the Device Authorization Response (RFC 8628, Sec. 3.2).
1023 */
1024static bool
1025parse_device_authz(struct async_ctx *actx, struct device_authz *authz)
1026{
1027 struct json_field fields[] = {
1028 {"device_code", JSON_TOKEN_STRING, {&authz->device_code}, PG_OAUTH_REQUIRED},
1029 {"user_code", JSON_TOKEN_STRING, {&authz->user_code}, PG_OAUTH_REQUIRED},
1030 {"verification_uri", JSON_TOKEN_STRING, {&authz->verification_uri}, PG_OAUTH_REQUIRED},
1031 {"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 {"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 {"verification_uri_complete", JSON_TOKEN_STRING, {&authz->verification_uri_complete}, PG_OAUTH_OPTIONAL},
1045 {"interval", JSON_TOKEN_NUMBER, {&authz->interval_str}, PG_OAUTH_OPTIONAL},
1046
1047 {0},
1048 };
1049
1050 if (!parse_oauth_json(actx, fields))
1051 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 if (authz->interval_str)
1058 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 authz->interval = 5;
1066 }
1067
1068 Assert(authz->expires_in_str); /* ensured by parse_oauth_json() */
1069 authz->expires_in = parse_expires_in(actx, authz->expires_in_str);
1070
1071 return true;
1072}
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 */
1078static bool
1080{
1081 bool result;
1082 struct json_field fields[] = {
1083 {"error", JSON_TOKEN_STRING, {&err->error}, PG_OAUTH_REQUIRED},
1084
1085 {"error_description", JSON_TOKEN_STRING, {&err->error_description}, PG_OAUTH_OPTIONAL},
1086
1087 {0},
1088 };
1089
1090 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 if (!result)
1097 actx->errctx = libpq_gettext("failed to parse token error response");
1098
1099 return result;
1100}
1101
1102/*
1103 * Constructs a message from the token error response and puts it into
1104 * actx->errbuf.
1105 */
1106static void
1107record_token_error(struct async_ctx *actx, const struct token_error *err)
1108{
1109 if (err->error_description)
1110 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 long response_code;
1119
1120 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, response_code = 0);
1121
1122 if (response_code == 401)
1123 {
1124 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 actx_error_str(actx, " ");
1128 }
1129 }
1130
1131 appendPQExpBuffer(&actx->errbuf, "(%s)", err->error);
1132}
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 */
1138static bool
1139parse_access_token(struct async_ctx *actx, struct token *tok)
1140{
1141 struct json_field fields[] = {
1142 {"access_token", JSON_TOKEN_STRING, {&tok->access_token}, PG_OAUTH_REQUIRED},
1143 {"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},
1162 };
1163
1164 return parse_oauth_json(actx, fields);
1165}
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 */
1180static bool
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 actx->mux = kqueue();
1209 if (actx->mux < 0)
1210 {
1211 actx_error_internal(actx, "failed to create kqueue: %m");
1212 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 actx->timerfd = kqueue();
1222 if (actx->timerfd < 0)
1223 {
1224 actx_error_internal(actx, "failed to create timer kqueue: %m");
1225 return false;
1226 }
1227
1228 return true;
1229#else
1230#error setup_multiplexer is not implemented on this platform
1231#endif
1232}
1233
1234/*
1235 * Adds and removes sockets from the multiplexer set, as directed by the
1236 * libcurl multi handle.
1237 */
1238static int
1239register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
1240 void *socketp)
1241{
1242 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 struct kevent ev[2];
1302 struct kevent ev_out[2];
1303 struct timespec timeout = {0};
1304 int nev = 0;
1305 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 switch (what)
1313 {
1314 case CURL_POLL_IN:
1315 EV_SET(&ev[nev], socket, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, 0);
1316 nev++;
1317 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_DELETE | EV_RECEIPT, 0, 0, 0);
1318 nev++;
1319 break;
1320
1321 case CURL_POLL_OUT:
1322 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_ADD | EV_RECEIPT, 0, 0, 0);
1323 nev++;
1324 EV_SET(&ev[nev], socket, EVFILT_READ, EV_DELETE | EV_RECEIPT, 0, 0, 0);
1325 nev++;
1326 break;
1327
1328 case CURL_POLL_INOUT:
1329 EV_SET(&ev[nev], socket, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, 0);
1330 nev++;
1331 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_ADD | EV_RECEIPT, 0, 0, 0);
1332 nev++;
1333 break;
1334
1335 case CURL_POLL_REMOVE:
1336 EV_SET(&ev[nev], socket, EVFILT_READ, EV_DELETE | EV_RECEIPT, 0, 0, 0);
1337 nev++;
1338 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_DELETE | EV_RECEIPT, 0, 0, 0);
1339 nev++;
1340 break;
1341
1342 default:
1343 actx_error_internal(actx, "unknown libcurl socket operation: %d", what);
1344 return -1;
1345 }
1346
1347 Assert(nev <= lengthof(ev));
1348 Assert(nev <= lengthof(ev_out));
1349
1350 res = kevent(actx->mux, ev, nev, ev_out, nev, &timeout);
1351 if (res < 0)
1352 {
1353 actx_error_internal(actx, "could not modify kqueue: %m");
1354 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 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 Assert(ev_out[i].flags & EV_ERROR);
1370
1371 errno = ev_out[i].data;
1372 if (errno && errno != ENOENT)
1373 {
1374 switch (what)
1375 {
1376 case CURL_POLL_REMOVE:
1377 actx_error_internal(actx, "could not delete from kqueue: %m");
1378 break;
1379 default:
1380 actx_error_internal(actx, "could not add to kqueue: %m");
1381 }
1382 return -1;
1383 }
1384 }
1385
1386 return 0;
1387#else
1388#error register_socket is not implemented on this platform
1389#endif
1390}
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 */
1404static bool
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 struct timespec timeout = {0};
1412 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 if (kevent(actx->mux, NULL, 0, &ev, 1, &timeout) < 0)
1428 {
1429 actx_error_internal(actx, "could not comb kqueue: %m");
1430 return false;
1431 }
1432
1433 return true;
1434#else
1435#error comb_multiplexer is not implemented on this platform
1436#endif
1437}
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 */
1452static bool
1453set_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 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 EV_SET(&ev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, 0);
1507 if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1508 {
1509 actx_error_internal(actx, "deleting kqueue timer: %m");
1510 return false;
1511 }
1512
1513 EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_DELETE, 0, 0, 0);
1514 if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1515 {
1516 actx_error_internal(actx, "removing kqueue timer from multiplexer: %m");
1517 return false;
1518 }
1519
1520 /* If we're not adding a timer, we're done. */
1521 if (timeout < 0)
1522 return true;
1523
1524 EV_SET(&ev, 1, EVFILT_TIMER, (EV_ADD | EV_ONESHOT), 0, timeout, 0);
1525 if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0)
1526 {
1527 actx_error_internal(actx, "setting kqueue timer to %ld: %m", timeout);
1528 return false;
1529 }
1530
1531 EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_ADD, 0, 0, 0);
1532 if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0)
1533 {
1534 actx_error_internal(actx, "adding kqueue timer to multiplexer: %m");
1535 return false;
1536 }
1537
1538 return true;
1539#else
1540#error set_timer is not implemented on this platform
1541#endif
1542}
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 */
1549static int
1551{
1552#if defined(HAVE_SYS_EPOLL_H) || defined(HAVE_SYS_EVENT_H)
1553 int res;
1554
1555 /* Is the timer ready? */
1556 res = PQsocketPoll(actx->timerfd, 1 /* forRead */ , 0, 0);
1557 if (res < 0)
1558 {
1559 actx_error(actx, "checking timer expiration: %m");
1560 return -1;
1561 }
1562
1563 return (res > 0);
1564#else
1565#error timer_expired is not implemented on this platform
1566#endif
1567}
1568
1569/*
1570 * Adds or removes timeouts from the multiplexer set, as directed by the
1571 * libcurl multi handle.
1572 */
1573static int
1574register_timer(CURLM *curlm, long timeout, void *ctx)
1575{
1576 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 if (!set_timer(actx, timeout))
1586 return -1; /* actx_error already called */
1587
1588 return 0;
1589}
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 */
1595static bool
1596drain_timer_events(struct async_ctx *actx, bool *was_expired)
1597{
1598 int res;
1599
1600 res = timer_expired(actx);
1601 if (res < 0)
1602 return false;
1603
1604 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 if (!set_timer(actx, -1))
1612 return false;
1613 }
1614
1615 if (was_expired)
1616 *was_expired = (res > 0);
1617
1618 return true;
1619}
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 */
1627static int
1628debug_callback(CURL *handle, curl_infotype type, char *data, size_t size,
1629 void *clientp)
1630{
1631 const char *prefix;
1632 bool printed_prefix = false;
1634
1635 /* Prefixes are modeled off of the default libcurl debug output. */
1636 switch (type)
1637 {
1638 case CURLINFO_TEXT:
1639 prefix = "*";
1640 break;
1641
1642 case CURLINFO_HEADER_IN: /* fall through */
1643 case CURLINFO_DATA_IN:
1644 prefix = "<";
1645 break;
1646
1647 case CURLINFO_HEADER_OUT: /* fall through */
1648 case CURLINFO_DATA_OUT:
1649 prefix = ">";
1650 break;
1651
1652 default:
1653 return 0;
1654 }
1655
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 for (int i = 0; i < size; i++)
1664 {
1665 char c = data[i];
1666
1667 if (!printed_prefix)
1668 {
1669 appendPQExpBuffer(&buf, "[libcurl] %s ", prefix);
1670 printed_prefix = true;
1671 }
1672
1673 if (c >= 0x20 && c <= 0x7E)
1675 else if ((type == CURLINFO_HEADER_IN
1676 || type == CURLINFO_HEADER_OUT
1677 || type == CURLINFO_TEXT)
1678 && (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 }
1685 else
1686 appendPQExpBuffer(&buf, "<%02X>", c);
1687
1688 if (c == '\n')
1689 {
1691 printed_prefix = false;
1692 }
1693 }
1694
1695 if (printed_prefix)
1696 appendPQExpBufferChar(&buf, '\n'); /* finish the line */
1697
1698 fprintf(stderr, "%s", buf.data);
1700 return 0;
1701}
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 */
1710static bool
1712{
1713 /*
1714 * Create our multi handle. This encapsulates the entire conversation with
1715 * libcurl for this connection.
1716 */
1717 actx->curlm = curl_multi_init();
1718 if (!actx->curlm)
1719 {
1720 /* We don't get a lot of feedback on the failure reason. */
1721 actx_error(actx, "failed to create libcurl multi handle");
1722 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 CHECK_MSETOPT(actx, CURLMOPT_SOCKETFUNCTION, register_socket, return false);
1730 CHECK_MSETOPT(actx, CURLMOPT_SOCKETDATA, actx, return false);
1731 CHECK_MSETOPT(actx, CURLMOPT_TIMERFUNCTION, register_timer, return false);
1732 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 actx->curl = curl_easy_init();
1739 if (!actx->curl)
1740 {
1741 actx_error(actx, "failed to create libcurl handle");
1742 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 CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L, return false);
1761
1762 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 CHECK_SETOPT(actx, CURLOPT_DEBUGFUNCTION, debug_callback, return false);
1770 CHECK_SETOPT(actx, CURLOPT_VERBOSE, 1L, return false);
1771 }
1772
1773 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 const CURLoption popt = CURLOPT_PROTOCOLS_STR;
1786 const char *protos = "https";
1787 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 if (actx->debugging)
1795 protos = unsafe;
1796
1797 CHECK_SETOPT(actx, popt, protos, return false);
1798 }
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 if (actx->debugging)
1809 {
1810 const char *env;
1811
1812 if ((env = getenv("PGOAUTHCAFILE")) != NULL)
1813 CHECK_SETOPT(actx, CURLOPT_CAINFO, env, return false);
1814 }
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 actx->headers = curl_slist_append(actx->headers, "Accept:");
1823 if (actx->headers == NULL)
1824 {
1825 actx_error(actx, "out of memory");
1826 return false;
1827 }
1828 CHECK_SETOPT(actx, CURLOPT_HTTPHEADER, actx->headers, return false);
1829
1830 return true;
1831}
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 */
1843static size_t
1844append_data(char *buf, size_t size, size_t nmemb, void *userdata)
1845{
1846 struct async_ctx *actx = userdata;
1847 PQExpBuffer resp = &actx->work_data;
1848 size_t len = size * nmemb;
1849
1850 /* In case we receive data over the threshold, abort the transfer */
1851 if ((resp->len + len) > MAX_OAUTH_RESPONSE_SIZE)
1852 {
1853 actx_error(actx, "response is too large");
1854 return 0;
1855 }
1856
1857 /* The data passed from libcurl is not null-terminated */
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 if (PQExpBufferBroken(resp))
1865 {
1866 actx_error(actx, "out of memory");
1867 return 0;
1868 }
1869
1870 return len;
1871}
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 */
1883static bool
1885{
1886 CURLMcode err;
1887
1889 CHECK_SETOPT(actx, CURLOPT_WRITEFUNCTION, append_data, return false);
1890 CHECK_SETOPT(actx, CURLOPT_WRITEDATA, actx, return false);
1891
1892 err = curl_multi_add_handle(actx->curlm, actx->curl);
1893 if (err)
1894 {
1895 actx_error(actx, "failed to queue HTTP request: %s",
1896 curl_multi_strerror(err));
1897 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 err = curl_multi_socket_action(actx->curlm, CURL_SOCKET_TIMEOUT, 0, &actx->running);
1910 if (err)
1911 {
1912 actx_error(actx, "asynchronous HTTP request failed: %s",
1913 curl_multi_strerror(err));
1914 return false;
1915 }
1916
1917 return true;
1918}
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 */
1940{
1941 CURLMcode err;
1942 CURLMsg *msg;
1943 int msgs_left;
1944 bool done;
1945
1946 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 */
1965 curl_multi_socket_all(actx->curlm,
1966 &actx->running));
1967
1968 if (err)
1969 {
1970 actx_error(actx, "asynchronous HTTP request failed: %s",
1971 curl_multi_strerror(err));
1972 return PGRES_POLLING_FAILED;
1973 }
1974
1975 if (actx->running)
1976 {
1977 /* We'll come back again. */
1978 return PGRES_POLLING_READING;
1979 }
1980 }
1981
1982 done = false;
1983 while ((msg = curl_multi_info_read(actx->curlm, &msgs_left)) != NULL)
1984 {
1985 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 continue;
1992 }
1993
1994 /* First check the status of the request itself. */
1995 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 if (actx->errbuf.len == 0)
2002 actx_error_str(actx, curl_easy_strerror(msg->data.result));
2003
2004 return PGRES_POLLING_FAILED;
2005 }
2006
2007 /* Now remove the finished handle; we'll add it back later if needed. */
2008 err = curl_multi_remove_handle(actx->curlm, msg->easy_handle);
2009 if (err)
2010 {
2011 actx_error(actx, "libcurl easy handle removal failed: %s",
2012 curl_multi_strerror(err));
2013 return PGRES_POLLING_FAILED;
2014 }
2015
2016 done = true;
2017 }
2018
2019 /* Sanity check. */
2020 if (!done)
2021 {
2022 actx_error(actx, "no result was retrieved for the finished handle");
2023 return PGRES_POLLING_FAILED;
2024 }
2025
2026 return PGRES_POLLING_OK;
2027}
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 */
2037static void
2039{
2040 char *escaped;
2041 char *haystack;
2042 char *match;
2043
2044 /* The first parameter to curl_easy_escape is deprecated by Curl */
2045 escaped = curl_easy_escape(NULL, s, 0);
2046 if (!escaped)
2047 {
2048 termPQExpBuffer(buf); /* mark the buffer broken */
2049 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 haystack = escaped;
2059
2060 while ((match = strstr(haystack, "%20")) != NULL)
2061 {
2062 /* Append the unmatched portion, followed by the plus sign. */
2063 appendBinaryPQExpBuffer(buf, haystack, match - haystack);
2065
2066 /* Keep searching after the match. */
2067 haystack = match + 3 /* strlen("%20") */ ;
2068 }
2069
2070 /* Push the remainder of the string onto the buffer. */
2071 appendPQExpBufferStr(buf, haystack);
2072
2073 curl_free(escaped);
2074}
2075
2076/*
2077 * Convenience wrapper for encoding a single string. Returns NULL on allocation
2078 * failure.
2079 */
2080static char *
2081urlencode(const char *s)
2082{
2084
2087
2088 return PQExpBufferDataBroken(buf) ? NULL : buf.data;
2089}
2090
2091/*
2092 * Appends a key/value pair to the end of an application/x-www-form-urlencoded
2093 * list.
2094 */
2095static void
2096build_urlencoded(PQExpBuffer buf, const char *key, const char *value)
2097{
2098 if (buf->len)
2100
2104}
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 */
2124static bool
2125start_discovery(struct async_ctx *actx, const char *discovery_uri)
2126{
2127 CHECK_SETOPT(actx, CURLOPT_HTTPGET, 1L, return false);
2128 CHECK_SETOPT(actx, CURLOPT_URL, discovery_uri, return false);
2129
2130 return start_request(actx);
2131}
2132
2133static bool
2135{
2136 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 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2152
2153 if (response_code != 200)
2154 {
2155 actx_error(actx, "unexpected response code %ld", response_code);
2156 return false;
2157 }
2158
2159 /*
2160 * Pull the fields we care about from the document.
2161 */
2162 actx->errctx = libpq_gettext("failed to parse OpenID discovery document");
2163 if (!parse_provider(actx, &actx->provider))
2164 return false; /* error message already set */
2165
2166 /*
2167 * Fill in any defaults for OPTIONAL/RECOMMENDED fields we care about.
2168 */
2170 {
2171 /*
2172 * Per Section 3, the default is ["authorization_code", "implicit"].
2173 */
2174 struct curl_slist *temp = actx->provider.grant_types_supported;
2175
2176 temp = curl_slist_append(temp, "authorization_code");
2177 if (temp)
2178 {
2179 temp = curl_slist_append(temp, "implicit");
2180 }
2181
2182 if (!temp)
2183 {
2184 actx_error(actx, "out of memory");
2185 return false;
2186 }
2187
2188 actx->provider.grant_types_supported = temp;
2189 }
2190
2191 return true;
2192}
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 */
2198static bool
2200{
2201 const struct provider *provider = &actx->provider;
2202 const char *oauth_issuer_id = conn_oauth_issuer_id(conn);
2203
2204 Assert(oauth_issuer_id); /* ensured by setup_oauth_parameters() */
2205 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 if (strcmp(oauth_issuer_id, provider->issuer) != 0)
2225 {
2226 actx_error(actx,
2227 "the issuer identifier (%s) does not match oauth_issuer (%s)",
2228 provider->issuer, oauth_issuer_id);
2229 return false;
2230 }
2231
2232 return true;
2233}
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 */
2243static bool
2245{
2246 const struct provider *provider = &actx->provider;
2247
2248 Assert(provider->issuer); /* ensured by parse_provider() */
2249 Assert(provider->token_endpoint); /* ensured by parse_provider() */
2250
2252 {
2253 actx_error(actx,
2254 "issuer \"%s\" does not provide a device authorization endpoint",
2255 provider->issuer);
2256 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 if (!actx->debugging)
2275 {
2277 HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
2278 {
2279 actx_error(actx,
2280 "device authorization endpoint \"%s\" must use HTTPS",
2282 return false;
2283 }
2284
2286 HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
2287 {
2288 actx_error(actx,
2289 "token endpoint \"%s\" must use HTTPS",
2291 return false;
2292 }
2293 }
2294
2295 return true;
2296}
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 */
2302static bool
2304{
2305 const char *oauth_client_id = conn_oauth_client_id(conn);
2306 const char *oauth_client_secret = conn_oauth_client_secret(conn);
2307
2308 bool success = false;
2309 char *username = NULL;
2310 char *password = NULL;
2311
2312 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 username = urlencode(oauth_client_id);
2341 password = urlencode(oauth_client_secret);
2342
2343 if (!username || !password)
2344 {
2345 actx_error(actx, "out of memory");
2346 goto cleanup;
2347 }
2348
2349 CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_BASIC, goto cleanup);
2350 CHECK_SETOPT(actx, CURLOPT_USERNAME, username, goto cleanup);
2351 CHECK_SETOPT(actx, CURLOPT_PASSWORD, password, goto cleanup);
2352
2353 actx->used_basic_auth = true;
2354 }
2355 else
2356 {
2357 /*
2358 * If we're not otherwise authenticating, client_id is REQUIRED in the
2359 * request body.
2360 */
2361 build_urlencoded(reqbody, "client_id", oauth_client_id);
2362
2363 CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, goto cleanup);
2364 actx->used_basic_auth = false;
2365 }
2366
2367 success = true;
2368
2369cleanup:
2370 free(username);
2371 free(password);
2372
2373 return success;
2374}
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 */
2386static bool
2388{
2389 const char *oauth_scope = conn_oauth_scope(conn);
2390 const char *device_authz_uri = actx->provider.device_authorization_endpoint;
2391 PQExpBuffer work_buffer = &actx->work_data;
2392
2393 Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
2394 Assert(device_authz_uri); /* ensured by check_for_device_flow() */
2395
2396 /* Construct our request body. */
2397 resetPQExpBuffer(work_buffer);
2398 if (oauth_scope && oauth_scope[0])
2399 build_urlencoded(work_buffer, "scope", oauth_scope);
2400
2401 if (!add_client_identification(actx, work_buffer, conn))
2402 return false;
2403
2404 if (PQExpBufferBroken(work_buffer))
2405 {
2406 actx_error(actx, "out of memory");
2407 return false;
2408 }
2409
2410 /* Make our request. */
2411 CHECK_SETOPT(actx, CURLOPT_URL, device_authz_uri, return false);
2412 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2413
2414 return start_request(actx);
2415}
2416
2417static bool
2419{
2420 long response_code;
2421
2422 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 if (response_code == 200)
2429 {
2430 actx->errctx = libpq_gettext("failed to parse device authorization");
2431 if (!parse_device_authz(actx, &actx->authz))
2432 return false; /* error message already set */
2433
2434 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 if (response_code == 400 || response_code == 401)
2444 {
2445 struct token_error err = {0};
2446
2447 if (!parse_token_error(actx, &err))
2448 {
2450 return false;
2451 }
2452
2453 /* Copy the token error into the context error buffer */
2454 record_token_error(actx, &err);
2455
2457 return false;
2458 }
2459
2460 /* Any other response codes are considered invalid */
2461 actx_error(actx, "unexpected response code %ld", response_code);
2462 return false;
2463}
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 */
2474static bool
2476{
2477 const char *token_uri = actx->provider.token_endpoint;
2478 const char *device_code = actx->authz.device_code;
2479 PQExpBuffer work_buffer = &actx->work_data;
2480
2481 Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
2482 Assert(token_uri); /* ensured by parse_provider() */
2483 Assert(device_code); /* ensured by parse_device_authz() */
2484
2485 /* Construct our request body. */
2486 resetPQExpBuffer(work_buffer);
2487 build_urlencoded(work_buffer, "device_code", device_code);
2488 build_urlencoded(work_buffer, "grant_type", OAUTH_GRANT_TYPE_DEVICE_CODE);
2489
2490 if (!add_client_identification(actx, work_buffer, conn))
2491 return false;
2492
2493 if (PQExpBufferBroken(work_buffer))
2494 {
2495 actx_error(actx, "out of memory");
2496 return false;
2497 }
2498
2499 /* Make our request. */
2500 CHECK_SETOPT(actx, CURLOPT_URL, token_uri, return false);
2501 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2502
2503 return start_request(actx);
2504}
2505
2506static bool
2507finish_token_request(struct async_ctx *actx, struct token *tok)
2508{
2509 long response_code;
2510
2511 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 if (response_code == 200)
2517 {
2518 actx->errctx = libpq_gettext("failed to parse access token response");
2519 if (!parse_access_token(actx, tok))
2520 return false; /* error message already set */
2521
2522 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 if (response_code == 400 || response_code == 401)
2532 {
2533 if (!parse_token_error(actx, &tok->err))
2534 return false;
2535
2536 return true;
2537 }
2538
2539 /* Any other response codes are considered invalid */
2540 actx_error(actx, "unexpected response code %ld", response_code);
2541 return false;
2542}
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 */
2553static bool
2555{
2556 bool success = false;
2557 struct token tok = {0};
2558 const struct token_error *err;
2559
2560 if (!finish_token_request(actx, &tok))
2561 goto token_cleanup;
2562
2563 /* A successful token request gives either a token or an in-band error. */
2564 Assert(tok.access_token || tok.err.error);
2565
2566 if (tok.access_token)
2567 {
2568 *token = tok.access_token;
2569 tok.access_token = NULL;
2570
2571 success = true;
2572 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 err = &tok.err;
2580 if (strcmp(err->error, "authorization_pending") != 0 &&
2581 strcmp(err->error, "slow_down") != 0)
2582 {
2583 record_token_error(actx, err);
2584 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 if (strcmp(err->error, "slow_down") == 0)
2592 {
2593 int prev_interval = actx->authz.interval;
2594
2595 actx->authz.interval += 5;
2596 if (actx->authz.interval < prev_interval)
2597 {
2598 actx_error(actx, "slow_down interval overflow");
2599 goto token_cleanup;
2600 }
2601 }
2602
2603 success = true;
2604
2605token_cleanup:
2606 free_token(&tok);
2607 return success;
2608}
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 */
2614static bool
2616{
2617 int res;
2618 PGpromptOAuthDevice prompt = {
2620 .user_code = actx->authz.user_code,
2621 .verification_uri_complete = actx->authz.verification_uri_complete,
2622 .expires_in = actx->authz.expires_in,
2623 };
2625
2626 res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
2627
2628 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 fprintf(stderr, libpq_gettext("Visit %s and enter the code: %s\n"),
2635 prompt.verification_uri, prompt.user_code);
2636 }
2637 else if (res < 0)
2638 {
2639 actx_error(actx, "device prompt failed");
2640 return false;
2641 }
2642
2643 return true;
2644}
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 */
2659static bool
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 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 if (init_successful == PG_BOOL_YES)
2691 goto done;
2692 else if (init_successful == PG_BOOL_NO)
2693 {
2695 "curl_global_init previously failed during OAuth setup");
2696 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 if (curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32) != CURLE_OK)
2712 {
2714 "curl_global_init failed during OAuth setup");
2715 init_successful = PG_BOOL_NO;
2716 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 info = curl_version_info(CURLVERSION_NOW);
2729 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 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 init_successful = PG_BOOL_NO;
2741 goto done;
2742 }
2743#endif
2744
2745 init_successful = PG_BOOL_YES;
2746
2747done:
2748#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2750#endif
2751 return (init_successful == PG_BOOL_YES);
2752}
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 */
2770{
2772 struct async_ctx *actx;
2773 char *oauth_token = NULL;
2775
2776 if (!initialize_curl(conn))
2777 return PGRES_POLLING_FAILED;
2778
2779 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 actx = calloc(1, sizeof(*actx));
2787 if (!actx)
2788 {
2789 libpq_append_conn_error(conn, "out of memory");
2790 return PGRES_POLLING_FAILED;
2791 }
2792
2793 actx->mux = PGINVALID_SOCKET;
2794 actx->timerfd = -1;
2795
2796 /* Should we enable unsafe features? */
2798
2799 state->async_ctx = actx;
2800
2801 initPQExpBuffer(&actx->work_data);
2802 initPQExpBuffer(&actx->errbuf);
2803
2804 if (!setup_multiplexer(actx))
2805 goto error_return;
2806
2807 if (!setup_curl_handles(actx))
2808 goto error_return;
2809 }
2810
2811 actx = state->async_ctx;
2812
2813 do
2814 {
2815 /* By default, the multiplexer is the altsock. Reassign as desired. */
2816 set_conn_altsock(conn, actx->mux);
2817
2818 switch (actx->step)
2819 {
2820 case OAUTH_STEP_INIT:
2821 break;
2822
2826 {
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 if (!drain_timer_events(actx, NULL))
2842 goto error_return;
2843
2844 /* Move the request forward. */
2845 status = drive_request(actx);
2846
2847 if (status == PGRES_POLLING_FAILED)
2848 goto error_return;
2849 else if (status == PGRES_POLLING_OK)
2850 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 if (!comb_multiplexer(actx))
2861 goto error_return;
2862
2863 return status;
2864 }
2865
2867 {
2868 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 if (!drain_timer_events(actx, &expired))
2877 goto error_return;
2878
2879 if (!expired)
2880 {
2882 return PGRES_POLLING_READING;
2883 }
2884
2885 break;
2886 }
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 switch (actx->step)
2895 {
2896 case OAUTH_STEP_INIT:
2897 actx->errctx = libpq_gettext("failed to fetch OpenID discovery document");
2899 goto error_return;
2900
2901 actx->step = OAUTH_STEP_DISCOVERY;
2902 break;
2903
2905 if (!finish_discovery(actx))
2906 goto error_return;
2907
2908 if (!check_issuer(actx, conn))
2909 goto error_return;
2910
2911 actx->errctx = libpq_gettext("cannot run OAuth device authorization");
2912 if (!check_for_device_flow(actx))
2913 goto error_return;
2914
2915 actx->errctx = libpq_gettext("failed to obtain device authorization");
2916 if (!start_device_authz(actx, conn))
2917 goto error_return;
2918
2920 break;
2921
2923 if (!finish_device_authz(actx))
2924 goto error_return;
2925
2926 actx->errctx = libpq_gettext("failed to obtain access token");
2927 if (!start_token_request(actx, conn))
2928 goto error_return;
2929
2931 break;
2932
2934 if (!handle_token_response(actx, &oauth_token))
2935 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 set_conn_oauth_token(conn, oauth_token);
2942
2943 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 if (!prompt_user(actx, conn))
2950 goto error_return;
2951
2952 actx->user_prompted = true;
2953 }
2954
2955 if (oauth_token)
2956 break; /* done! */
2957
2958 /*
2959 * Wait for the required interval before issuing the next
2960 * request.
2961 */
2962 if (!set_timer(actx, actx->authz.interval * 1000))
2963 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 */
2971
2973 actx->running = 1;
2974 break;
2975
2977 actx->errctx = libpq_gettext("failed to obtain access token");
2978 if (!start_token_request(actx, conn))
2979 goto error_return;
2980
2982 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 } while (!oauth_token && !actx->running);
2991
2992 /* If we've stored a token, we're done. Otherwise come back later. */
2993 return oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
2994
2995error_return:
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 if (actx->errctx)
3003 appendPQExpBuffer(errbuf, "%s: ", actx->errctx);
3004
3005 if (PQExpBufferDataBroken(actx->errbuf))
3006 appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
3007 else
3009
3010 if (actx->curl_err[0])
3011 {
3012 appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
3013
3014 /* Sometimes libcurl adds a newline to the error buffer. :( */
3015 if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
3016 {
3017 errbuf->data[errbuf->len - 2] = ')';
3018 errbuf->data[errbuf->len - 1] = '\0';
3019 errbuf->len--;
3020 }
3021 }
3022
3024
3025 return PGRES_POLLING_FAILED;
3026}
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 */
3034{
3037 struct async_ctx *actx;
3038#ifndef WIN32
3039 sigset_t osigset;
3040 bool sigpipe_pending;
3041 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 masked = (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0);
3062#endif
3063
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 actx = state->async_ctx;
3075 Assert(actx || result == PGRES_POLLING_FAILED);
3076
3077 if (actx && actx->debugging)
3078 {
3079 actx->dbg_num_calls++;
3080 if (result == PGRES_POLLING_OK || result == PGRES_POLLING_FAILED)
3081 fprintf(stderr, "[libpq] total number of polls: %d\n",
3082 actx->dbg_num_calls);
3083 }
3084
3085#ifndef WIN32
3086 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 pq_reset_sigpipe(&osigset, sigpipe_pending, true /* EPIPE, maybe */ );
3093 }
3094#endif
3095
3096 return result;
3097}
static void cleanup(void)
Definition: bootstrap.c:717
#define gettext_noop(x)
Definition: c.h:1181
#define lengthof(array)
Definition: c.h:809
#define fprintf(file, fmt, msg)
Definition: cubescan.l:21
void err(int eval, const char *fmt,...)
Definition: err.c:43
PQauthDataHook_type PQgetAuthDataHook(void)
Definition: fe-auth.c:1589
int PQsocketPoll(int sock, int forRead, int forWrite, pg_usec_time_t end_time)
Definition: fe-misc.c:1141
Assert(PointerIsAligned(start, uint64))
static struct @171 value
static bool success
Definition: initdb.c:187
static char * username
Definition: initdb.c:153
#define close(a)
Definition: win32.h:12
int i
Definition: isn.c:77
JsonParseErrorType pg_parse_json(JsonLexContext *lex, const JsonSemAction *sem)
Definition: jsonapi.c:744
JsonLexContext * makeJsonLexContextCstringLen(JsonLexContext *lex, const char *json, size_t len, int encoding, bool need_escapes)
Definition: jsonapi.c:392
void setJsonLexContextOwnsTokens(JsonLexContext *lex, bool owned_by_context)
Definition: jsonapi.c:542
char * json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
Definition: jsonapi.c:2404
void freeJsonLexContext(JsonLexContext *lex)
Definition: jsonapi.c:687
JsonParseErrorType
Definition: jsonapi.h:35
@ JSON_OUT_OF_MEMORY
Definition: jsonapi.h:52
@ JSON_SEM_ACTION_FAILED
Definition: jsonapi.h:59
@ JSON_SUCCESS
Definition: jsonapi.h:36
JsonTokenType
Definition: jsonapi.h:18
@ JSON_TOKEN_NUMBER
Definition: jsonapi.h:21
@ JSON_TOKEN_STRING
Definition: jsonapi.h:20
@ JSON_TOKEN_ARRAY_START
Definition: jsonapi.h:24
int(* PQauthDataHook_type)(PGauthData type, PGconn *conn, void *data)
Definition: libpq-fe.h:814
PostgresPollingStatusType
Definition: libpq-fe.h:114
@ PGRES_POLLING_OK
Definition: libpq-fe.h:118
@ PGRES_POLLING_READING
Definition: libpq-fe.h:116
@ PGRES_POLLING_FAILED
Definition: libpq-fe.h:115
@ PQAUTHDATA_PROMPT_OAUTH_DEVICE
Definition: libpq-fe.h:194
PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn)
Definition: oauth-curl.c:3033
static bool drain_timer_events(struct async_ctx *actx, bool *was_expired)
Definition: oauth-curl.c:1596
static char * urlencode(const char *s)
Definition: oauth-curl.c:2081
static bool setup_multiplexer(struct async_ctx *actx)
Definition: oauth-curl.c:1181
static bool finish_token_request(struct async_ctx *actx, struct token *tok)
Definition: oauth-curl.c:2507
static JsonParseErrorType oauth_json_array_end(void *state)
Definition: oauth-curl.c:640
static void append_urlencoded(PQExpBuffer buf, const char *s)
Definition: oauth-curl.c:2038
static bool start_token_request(struct async_ctx *actx, PGconn *conn)
Definition: oauth-curl.c:2475
static bool initialize_curl(PGconn *conn)
Definition: oauth-curl.c:2660
#define HTTPS_SCHEME
Definition: oauth-curl.c:2235
#define MAX_OAUTH_RESPONSE_SIZE
Definition: oauth-curl.c:83
static bool parse_token_error(struct async_ctx *actx, struct token_error *err)
Definition: oauth-curl.c:1079
void pg_fe_cleanup_oauth_flow(PGconn *conn)
Definition: oauth-curl.c:356
static bool add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
Definition: oauth-curl.c:2303
static int parse_interval(struct async_ctx *actx, const char *interval_str)
Definition: oauth-curl.c:980
static void free_provider(struct provider *provider)
Definition: oauth-curl.c:123
static void build_urlencoded(PQExpBuffer buf, const char *key, const char *value)
Definition: oauth-curl.c:2096
#define PG_CURL_IGNORE_DEPRECATION(x)
Definition: oauth-curl.c:1932
#define conn_oauth_issuer_id(CONN)
Definition: oauth-curl.c:57
#define oauth_parse_set_error_internal(ctx, fmt,...)
Definition: oauth-curl.c:468
static void record_token_error(struct async_ctx *actx, const struct token_error *err)
Definition: oauth-curl.c:1107
static bool parse_device_authz(struct async_ctx *actx, struct device_authz *authz)
Definition: oauth-curl.c:1025
static void report_type_mismatch(struct oauth_parse *ctx)
Definition: oauth-curl.c:472
static int register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx, void *socketp)
Definition: oauth-curl.c:1239
#define PG_OAUTH_OPTIONAL
Definition: oauth-curl.c:453
static bool set_timer(struct async_ctx *actx, long timeout)
Definition: oauth-curl.c:1453
#define actx_error_internal(ACTX, FMT,...)
Definition: oauth-curl.c:378
static bool parse_access_token(struct async_ctx *actx, struct token *tok)
Definition: oauth-curl.c:1139
static int timer_expired(struct async_ctx *actx)
Definition: oauth-curl.c:1550
static PostgresPollingStatusType drive_request(struct async_ctx *actx)
Definition: oauth-curl.c:1939
static bool start_device_authz(struct async_ctx *actx, PGconn *conn)
Definition: oauth-curl.c:2387
static bool prompt_user(struct async_ctx *actx, PGconn *conn)
Definition: oauth-curl.c:2615
#define CHECK_MSETOPT(ACTX, OPT, VAL, FAILACTION)
Definition: oauth-curl.c:389
static bool finish_discovery(struct async_ctx *actx)
Definition: oauth-curl.c:2134
static double parse_json_number(const char *s)
Definition: oauth-curl.c:945
#define conn_oauth_discovery_uri(CONN)
Definition: oauth-curl.c:56
static bool start_discovery(struct async_ctx *actx, const char *discovery_uri)
Definition: oauth-curl.c:2125
static JsonParseErrorType oauth_json_object_field_start(void *state, char *name, bool isnull)
Definition: oauth-curl.c:530
static JsonParseErrorType oauth_json_scalar(void *state, char *token, JsonTokenType type)
Definition: oauth-curl.c:668
static void free_token_error(struct token_error *err)
Definition: oauth-curl.c:176
#define actx_error_str(ACTX, S)
Definition: oauth-curl.c:381
static bool finish_device_authz(struct async_ctx *actx)
Definition: oauth-curl.c:2418
#define conn_oauth_scope(CONN)
Definition: oauth-curl.c:58
static size_t append_data(char *buf, size_t size, size_t nmemb, void *userdata)
Definition: oauth-curl.c:1844
#define CHECK_SETOPT(ACTX, OPT, VAL, FAILACTION)
Definition: oauth-curl.c:400
static PostgresPollingStatusType pg_fe_run_oauth_flow_impl(PGconn *conn)
Definition: oauth-curl.c:2769
static bool parse_oauth_json(struct async_ctx *actx, const struct json_field *fields)
Definition: oauth-curl.c:827
#define MAX_OAUTH_NESTING_LEVEL
Definition: oauth-curl.c:97
#define OAUTH_GRANT_TYPE_DEVICE_CODE
Definition: oauth-curl.c:2236
#define conn_oauth_client_id(CONN)
Definition: oauth-curl.c:54
static JsonParseErrorType oauth_json_array_start(void *state)
Definition: oauth-curl.c:608
static JsonParseErrorType oauth_json_object_end(void *state)
Definition: oauth-curl.c:585
#define set_conn_altsock(CONN, VAL)
Definition: oauth-curl.c:61
static int debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *clientp)
Definition: oauth-curl.c:1628
static void free_token(struct token *tok)
Definition: oauth-curl.c:202
#define oauth_parse_set_error(ctx, fmt,...)
Definition: oauth-curl.c:465
static bool comb_multiplexer(struct async_ctx *actx)
Definition: oauth-curl.c:1405
OAuthStep
Definition: oauth-curl.c:215
@ OAUTH_STEP_DEVICE_AUTHORIZATION
Definition: oauth-curl.c:218
@ OAUTH_STEP_WAIT_INTERVAL
Definition: oauth-curl.c:220
@ OAUTH_STEP_INIT
Definition: oauth-curl.c:216
@ OAUTH_STEP_DISCOVERY
Definition: oauth-curl.c:217
@ OAUTH_STEP_TOKEN_REQUEST
Definition: oauth-curl.c:219
static int register_timer(CURLM *curlm, long timeout, void *ctx)
Definition: oauth-curl.c:1574
#define conn_oauth_client_secret(CONN)
Definition: oauth-curl.c:55
#define set_conn_oauth_token(CONN, VAL)
Definition: oauth-curl.c:62
#define CHECK_GETINFO(ACTX, INFO, OUT, FAILACTION)
Definition: oauth-curl.c:411
static bool check_content_type(struct async_ctx *actx, const char *type)
Definition: oauth-curl.c:768
static bool check_issuer(struct async_ctx *actx, PGconn *conn)
Definition: oauth-curl.c:2199
#define actx_error(ACTX, FMT,...)
Definition: oauth-curl.c:375
static bool parse_provider(struct async_ctx *actx, struct provider *provider)
Definition: oauth-curl.c:914
static bool start_request(struct async_ctx *actx)
Definition: oauth-curl.c:1884
static int parse_expires_in(struct async_ctx *actx, const char *expires_in_str)
Definition: oauth-curl.c:1006
static void free_async_ctx(PGconn *conn, struct async_ctx *actx)
Definition: oauth-curl.c:289
static void free_device_authz(struct device_authz *authz)
Definition: oauth-curl.c:151
#define conn_sasl_state(CONN)
Definition: oauth-curl.c:59
#define conn_errorMessage(CONN)
Definition: oauth-curl.c:53
static bool handle_token_response(struct async_ctx *actx, char **token)
Definition: oauth-curl.c:2554
static JsonParseErrorType oauth_json_object_start(void *state)
Definition: oauth-curl.c:505
#define PG_OAUTH_REQUIRED
Definition: oauth-curl.c:452
static bool check_for_device_flow(struct async_ctx *actx)
Definition: oauth-curl.c:2244
static bool setup_curl_handles(struct async_ctx *actx)
Definition: oauth-curl.c:1711
void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
Definition: oauth-utils.c:208
int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
Definition: oauth-utils.c:172
void libpq_append_conn_error(PGconn *conn, const char *fmt,...)
Definition: oauth-utils.c:95
bool oauth_unsafe_debugging_enabled(void)
Definition: oauth-utils.c:149
#define libpq_gettext(x)
Definition: oauth-utils.h:86
PGTernaryBool
Definition: oauth-utils.h:72
@ PG_BOOL_YES
Definition: oauth-utils.h:74
@ PG_BOOL_NO
Definition: oauth-utils.h:75
@ PG_BOOL_UNKNOWN
Definition: oauth-utils.h:73
#define pglock_thread()
Definition: oauth-utils.h:91
#define pgunlock_thread()
Definition: oauth-utils.h:92
const void size_t len
const void * data
static char buf[DEFAULT_XLOG_SEG_SIZE]
Definition: pg_test_fsync.c:71
@ PG_UTF8
Definition: pg_wchar.h:232
int pgsocket
Definition: port.h:29
#define PGINVALID_SOCKET
Definition: port.h:31
int pg_strncasecmp(const char *s1, const char *s2, size_t n)
Definition: pgstrcasecmp.c:65
void initPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:90
void resetPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:146
void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
Definition: pqexpbuffer.c:265
void appendBinaryPQExpBuffer(PQExpBuffer str, const char *data, size_t datalen)
Definition: pqexpbuffer.c:397
void appendPQExpBufferChar(PQExpBuffer str, char ch)
Definition: pqexpbuffer.c:378
void appendPQExpBufferStr(PQExpBuffer str, const char *data)
Definition: pqexpbuffer.c:367
void termPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:129
#define PQExpBufferBroken(str)
Definition: pqexpbuffer.h:59
#define PQExpBufferDataBroken(buf)
Definition: pqexpbuffer.h:67
char * c
#define calloc(a, b)
#define free(a)
static char * password
Definition: streamutil.c:51
PGconn * conn
Definition: streamutil.c:52
json_struct_action array_end
Definition: jsonapi.h:157
json_struct_action object_start
Definition: jsonapi.h:154
json_ofield_action object_field_start
Definition: jsonapi.h:158
json_scalar_action scalar
Definition: jsonapi.h:162
void * semstate
Definition: jsonapi.h:153
json_struct_action array_start
Definition: jsonapi.h:156
json_struct_action object_end
Definition: jsonapi.h:155
const char * verification_uri
Definition: libpq-fe.h:734
const char * user_code
Definition: libpq-fe.h:735
int running
Definition: oauth-curl.c:278
struct device_authz authz
Definition: oauth-curl.c:276
CURL * curl
Definition: oauth-curl.c:238
PQExpBufferData work_data
Definition: oauth-curl.c:242
bool user_prompted
Definition: oauth-curl.c:279
pgsocket mux
Definition: oauth-curl.c:233
PQExpBufferData errbuf
Definition: oauth-curl.c:268
int timerfd
Definition: oauth-curl.c:232
bool debugging
Definition: oauth-curl.c:281
CURLM * curlm
Definition: oauth-curl.c:236
enum OAuthStep step
Definition: oauth-curl.c:230
struct provider provider
Definition: oauth-curl.c:275
int dbg_num_calls
Definition: oauth-curl.c:282
char curl_err[CURL_ERROR_SIZE]
Definition: oauth-curl.c:269
const char * errctx
Definition: oauth-curl.c:267
bool used_basic_auth
Definition: oauth-curl.c:280
struct curl_slist * headers
Definition: oauth-curl.c:241
char * interval_str
Definition: oauth-curl.c:143
char * user_code
Definition: oauth-curl.c:139
char * device_code
Definition: oauth-curl.c:138
char * expires_in_str
Definition: oauth-curl.c:142
char * verification_uri_complete
Definition: oauth-curl.c:141
char * verification_uri
Definition: oauth-curl.c:140
const char * name
Definition: oauth-curl.c:437
struct curl_slist ** array
Definition: oauth-curl.c:445
bool required
Definition: oauth-curl.c:448
char ** scalar
Definition: oauth-curl.c:444
JsonTokenType type
Definition: oauth-curl.c:439
const struct json_field * active
Definition: oauth-curl.c:462
const struct json_field * fields
Definition: oauth-curl.c:461
PQExpBuffer errbuf
Definition: oauth-curl.c:458
char * device_authorization_endpoint
Definition: oauth-curl.c:118
struct curl_slist * grant_types_supported
Definition: oauth-curl.c:119
char * issuer
Definition: oauth-curl.c:116
char * token_endpoint
Definition: oauth-curl.c:117
Definition: regguts.h:323
char * error_description
Definition: oauth-curl.c:172
char * error
Definition: oauth-curl.c:171
struct token_error err
Definition: oauth-curl.c:198
char * token_type
Definition: oauth-curl.c:195
char * access_token
Definition: oauth-curl.c:194
static JsonSemAction sem
const char * type
const char * name
int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len)
Definition: wchar.c:2202
#define socket(af, type, protocol)
Definition: win32_port.h:495