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