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