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