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