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#ifdef HAVE_SYS_EPOLL_H
1176 struct async_ctx *actx = ctx;
1177 struct epoll_event ev = {0};
1178 int res;
1179 int op = EPOLL_CTL_ADD;
1180
1181 switch (what)
1182 {
1183 case CURL_POLL_IN:
1184 ev.events = EPOLLIN;
1185 break;
1186
1187 case CURL_POLL_OUT:
1188 ev.events = EPOLLOUT;
1189 break;
1190
1191 case CURL_POLL_INOUT:
1192 ev.events = EPOLLIN | EPOLLOUT;
1193 break;
1194
1195 case CURL_POLL_REMOVE:
1196 op = EPOLL_CTL_DEL;
1197 break;
1198
1199 default:
1200 actx_error(actx, "unknown libcurl socket operation: %d", what);
1201 return -1;
1202 }
1203
1204 res = epoll_ctl(actx->mux, op, socket, &ev);
1205 if (res < 0 && errno == EEXIST)
1206 {
1207 /* We already had this socket in the pollset. */
1208 op = EPOLL_CTL_MOD;
1209 res = epoll_ctl(actx->mux, op, socket, &ev);
1210 }
1211
1212 if (res < 0)
1213 {
1214 switch (op)
1215 {
1216 case EPOLL_CTL_ADD:
1217 actx_error(actx, "could not add to epoll set: %m");
1218 break;
1219
1220 case EPOLL_CTL_DEL:
1221 actx_error(actx, "could not delete from epoll set: %m");
1222 break;
1223
1224 default:
1225 actx_error(actx, "could not update epoll set: %m");
1226 }
1227
1228 return -1;
1229 }
1230
1231 return 0;
1232#endif
1233#ifdef HAVE_SYS_EVENT_H
1234 struct async_ctx *actx = ctx;
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 */
1330static bool
1331set_timer(struct async_ctx *actx, long timeout)
1332{
1333#if HAVE_SYS_EPOLL_H
1334 struct itimerspec spec = {0};
1335
1336 if (timeout < 0)
1337 {
1338 /* the zero itimerspec will disarm the timer below */
1339 }
1340 else if (timeout == 0)
1341 {
1342 /*
1343 * A zero timeout means libcurl wants us to call back immediately.
1344 * That's not technically an option for timerfd, but we can make the
1345 * timeout ridiculously short.
1346 */
1347 spec.it_value.tv_nsec = 1;
1348 }
1349 else
1350 {
1351 spec.it_value.tv_sec = timeout / 1000;
1352 spec.it_value.tv_nsec = (timeout % 1000) * 1000000;
1353 }
1354
1355 if (timerfd_settime(actx->timerfd, 0 /* no flags */ , &spec, NULL) < 0)
1356 {
1357 actx_error(actx, "setting timerfd to %ld: %m", timeout);
1358 return false;
1359 }
1360
1361 return true;
1362#endif
1363#ifdef HAVE_SYS_EVENT_H
1364 struct kevent ev;
1365
1366#ifdef __NetBSD__
1367
1368 /*
1369 * Work around NetBSD's rejection of zero timeouts (EINVAL), a bit like
1370 * timerfd above.
1371 */
1372 if (timeout == 0)
1373 timeout = 1;
1374#endif
1375
1376 /* Enable/disable the timer itself. */
1377 EV_SET(&ev, 1, EVFILT_TIMER, timeout < 0 ? EV_DELETE : (EV_ADD | EV_ONESHOT),
1378 0, timeout, 0);
1379 if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1380 {
1381 actx_error(actx, "setting kqueue timer to %ld: %m", timeout);
1382 return false;
1383 }
1384
1385 /*
1386 * Add/remove the timer to/from the mux. (In contrast with epoll, if we
1387 * allowed the timer to remain registered here after being disabled, the
1388 * mux queue would retain any previous stale timeout notifications and
1389 * remain readable.)
1390 */
1391 EV_SET(&ev, actx->timerfd, EVFILT_READ, timeout < 0 ? EV_DELETE : EV_ADD,
1392 0, 0, 0);
1393 if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1394 {
1395 actx_error(actx, "could not update timer on kqueue: %m");
1396 return false;
1397 }
1398
1399 return true;
1400#endif
1401
1402 actx_error(actx, "libpq does not support timers on this platform");
1403 return false;
1404}
1405
1406/*
1407 * Returns 1 if the timeout in the multiplexer set has expired since the last
1408 * call to set_timer(), 0 if the timer is still running, or -1 (with an
1409 * actx_error() report) if the timer cannot be queried.
1410 */
1411static int
1413{
1414#if HAVE_SYS_EPOLL_H
1415 struct itimerspec spec = {0};
1416
1417 if (timerfd_gettime(actx->timerfd, &spec) < 0)
1418 {
1419 actx_error(actx, "getting timerfd value: %m");
1420 return -1;
1421 }
1422
1423 /*
1424 * This implementation assumes we're using single-shot timers. If you
1425 * change to using intervals, you'll need to reimplement this function
1426 * too, possibly with the read() or select() interfaces for timerfd.
1427 */
1428 Assert(spec.it_interval.tv_sec == 0
1429 && spec.it_interval.tv_nsec == 0);
1430
1431 /* If the remaining time to expiration is zero, we're done. */
1432 return (spec.it_value.tv_sec == 0
1433 && spec.it_value.tv_nsec == 0);
1434#endif
1435#ifdef HAVE_SYS_EVENT_H
1436 int res;
1437
1438 /* Is the timer queue ready? */
1439 res = PQsocketPoll(actx->timerfd, 1 /* forRead */ , 0, 0);
1440 if (res < 0)
1441 {
1442 actx_error(actx, "checking kqueue for timeout: %m");
1443 return -1;
1444 }
1445
1446 return (res > 0);
1447#endif
1448
1449 actx_error(actx, "libpq does not support timers on this platform");
1450 return -1;
1451}
1452
1453/*
1454 * Adds or removes timeouts from the multiplexer set, as directed by the
1455 * libcurl multi handle.
1456 */
1457static int
1458register_timer(CURLM *curlm, long timeout, void *ctx)
1459{
1460 struct async_ctx *actx = ctx;
1461
1462 /*
1463 * There might be an optimization opportunity here: if timeout == 0, we
1464 * could signal drive_request to immediately call
1465 * curl_multi_socket_action, rather than returning all the way up the
1466 * stack only to come right back. But it's not clear that the additional
1467 * code complexity is worth it.
1468 */
1469 if (!set_timer(actx, timeout))
1470 return -1; /* actx_error already called */
1471
1472 return 0;
1473}
1474
1475/*
1476 * Prints Curl request debugging information to stderr.
1477 *
1478 * Note that this will expose a number of critical secrets, so users have to opt
1479 * into this (see PGOAUTHDEBUG).
1480 */
1481static int
1482debug_callback(CURL *handle, curl_infotype type, char *data, size_t size,
1483 void *clientp)
1484{
1485 const char *prefix;
1486 bool printed_prefix = false;
1488
1489 /* Prefixes are modeled off of the default libcurl debug output. */
1490 switch (type)
1491 {
1492 case CURLINFO_TEXT:
1493 prefix = "*";
1494 break;
1495
1496 case CURLINFO_HEADER_IN: /* fall through */
1497 case CURLINFO_DATA_IN:
1498 prefix = "<";
1499 break;
1500
1501 case CURLINFO_HEADER_OUT: /* fall through */
1502 case CURLINFO_DATA_OUT:
1503 prefix = ">";
1504 break;
1505
1506 default:
1507 return 0;
1508 }
1509
1511
1512 /*
1513 * Split the output into lines for readability; sometimes multiple headers
1514 * are included in a single call. We also don't allow unprintable ASCII
1515 * through without a basic <XX> escape.
1516 */
1517 for (int i = 0; i < size; i++)
1518 {
1519 char c = data[i];
1520
1521 if (!printed_prefix)
1522 {
1523 appendPQExpBuffer(&buf, "[libcurl] %s ", prefix);
1524 printed_prefix = true;
1525 }
1526
1527 if (c >= 0x20 && c <= 0x7E)
1529 else if ((type == CURLINFO_HEADER_IN
1530 || type == CURLINFO_HEADER_OUT
1531 || type == CURLINFO_TEXT)
1532 && (c == '\r' || c == '\n'))
1533 {
1534 /*
1535 * Don't bother emitting <0D><0A> for headers and text; it's not
1536 * helpful noise.
1537 */
1538 }
1539 else
1540 appendPQExpBuffer(&buf, "<%02X>", c);
1541
1542 if (c == '\n')
1543 {
1545 printed_prefix = false;
1546 }
1547 }
1548
1549 if (printed_prefix)
1550 appendPQExpBufferChar(&buf, '\n'); /* finish the line */
1551
1552 fprintf(stderr, "%s", buf.data);
1554 return 0;
1555}
1556
1557/*
1558 * Initializes the two libcurl handles in the async_ctx. The multi handle,
1559 * actx->curlm, is what drives the asynchronous engine and tells us what to do
1560 * next. The easy handle, actx->curl, encapsulates the state for a single
1561 * request/response. It's added to the multi handle as needed, during
1562 * start_request().
1563 */
1564static bool
1566{
1567 /*
1568 * Create our multi handle. This encapsulates the entire conversation with
1569 * libcurl for this connection.
1570 */
1571 actx->curlm = curl_multi_init();
1572 if (!actx->curlm)
1573 {
1574 /* We don't get a lot of feedback on the failure reason. */
1575 actx_error(actx, "failed to create libcurl multi handle");
1576 return false;
1577 }
1578
1579 /*
1580 * The multi handle tells us what to wait on using two callbacks. These
1581 * will manipulate actx->mux as needed.
1582 */
1583 CHECK_MSETOPT(actx, CURLMOPT_SOCKETFUNCTION, register_socket, return false);
1584 CHECK_MSETOPT(actx, CURLMOPT_SOCKETDATA, actx, return false);
1585 CHECK_MSETOPT(actx, CURLMOPT_TIMERFUNCTION, register_timer, return false);
1586 CHECK_MSETOPT(actx, CURLMOPT_TIMERDATA, actx, return false);
1587
1588 /*
1589 * Set up an easy handle. All of our requests are made serially, so we
1590 * only ever need to keep track of one.
1591 */
1592 actx->curl = curl_easy_init();
1593 if (!actx->curl)
1594 {
1595 actx_error(actx, "failed to create libcurl handle");
1596 return false;
1597 }
1598
1599 /*
1600 * Multi-threaded applications must set CURLOPT_NOSIGNAL. This requires us
1601 * to handle the possibility of SIGPIPE ourselves using pq_block_sigpipe;
1602 * see pg_fe_run_oauth_flow().
1603 *
1604 * NB: If libcurl is not built against a friendly DNS resolver (c-ares or
1605 * threaded), setting this option prevents DNS lookups from timing out
1606 * correctly. We warn about this situation at configure time.
1607 *
1608 * TODO: Perhaps there's a clever way to warn the user about synchronous
1609 * DNS at runtime too? It's not immediately clear how to do that in a
1610 * helpful way: for many standard single-threaded use cases, the user
1611 * might not care at all, so spraying warnings to stderr would probably do
1612 * more harm than good.
1613 */
1614 CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L, return false);
1615
1616 if (actx->debugging)
1617 {
1618 /*
1619 * Set a callback for retrieving error information from libcurl, the
1620 * function only takes effect when CURLOPT_VERBOSE has been set so
1621 * make sure the order is kept.
1622 */
1623 CHECK_SETOPT(actx, CURLOPT_DEBUGFUNCTION, debug_callback, return false);
1624 CHECK_SETOPT(actx, CURLOPT_VERBOSE, 1L, return false);
1625 }
1626
1627 CHECK_SETOPT(actx, CURLOPT_ERRORBUFFER, actx->curl_err, return false);
1628
1629 /*
1630 * Only HTTPS is allowed. (Debug mode additionally allows HTTP; this is
1631 * intended for testing only.)
1632 *
1633 * There's a bit of unfortunate complexity around the choice of
1634 * CURLoption. CURLOPT_PROTOCOLS is deprecated in modern Curls, but its
1635 * replacement didn't show up until relatively recently.
1636 */
1637 {
1638#if CURL_AT_LEAST_VERSION(7, 85, 0)
1639 const CURLoption popt = CURLOPT_PROTOCOLS_STR;
1640 const char *protos = "https";
1641 const char *const unsafe = "https,http";
1642#else
1643 const CURLoption popt = CURLOPT_PROTOCOLS;
1644 long protos = CURLPROTO_HTTPS;
1645 const long unsafe = CURLPROTO_HTTPS | CURLPROTO_HTTP;
1646#endif
1647
1648 if (actx->debugging)
1649 protos = unsafe;
1650
1651 CHECK_SETOPT(actx, popt, protos, return false);
1652 }
1653
1654 /*
1655 * If we're in debug mode, allow the developer to change the trusted CA
1656 * list. For now, this is not something we expose outside of the UNSAFE
1657 * mode, because it's not clear that it's useful in production: both libpq
1658 * and the user's browser must trust the same authorization servers for
1659 * the flow to work at all, so any changes to the roots are likely to be
1660 * done system-wide.
1661 */
1662 if (actx->debugging)
1663 {
1664 const char *env;
1665
1666 if ((env = getenv("PGOAUTHCAFILE")) != NULL)
1667 CHECK_SETOPT(actx, CURLOPT_CAINFO, env, return false);
1668 }
1669
1670 /*
1671 * Suppress the Accept header to make our request as minimal as possible.
1672 * (Ideally we would set it to "application/json" instead, but OpenID is
1673 * pretty strict when it comes to provider behavior, so we have to check
1674 * what comes back anyway.)
1675 */
1676 actx->headers = curl_slist_append(actx->headers, "Accept:");
1677 if (actx->headers == NULL)
1678 {
1679 actx_error(actx, "out of memory");
1680 return false;
1681 }
1682 CHECK_SETOPT(actx, CURLOPT_HTTPHEADER, actx->headers, return false);
1683
1684 return true;
1685}
1686
1687/*
1688 * Generic HTTP Request Handlers
1689 */
1690
1691/*
1692 * Response callback from libcurl which appends the response body into
1693 * actx->work_data (see start_request()). The maximum size of the data is
1694 * defined by CURL_MAX_WRITE_SIZE which by default is 16kb (and can only be
1695 * changed by recompiling libcurl).
1696 */
1697static size_t
1698append_data(char *buf, size_t size, size_t nmemb, void *userdata)
1699{
1700 struct async_ctx *actx = userdata;
1701 PQExpBuffer resp = &actx->work_data;
1702 size_t len = size * nmemb;
1703
1704 /* In case we receive data over the threshold, abort the transfer */
1705 if ((resp->len + len) > MAX_OAUTH_RESPONSE_SIZE)
1706 {
1707 actx_error(actx, "response is too large");
1708 return 0;
1709 }
1710
1711 /* The data passed from libcurl is not null-terminated */
1713
1714 /*
1715 * Signal an error in order to abort the transfer in case we ran out of
1716 * memory in accepting the data.
1717 */
1718 if (PQExpBufferBroken(resp))
1719 {
1720 actx_error(actx, "out of memory");
1721 return 0;
1722 }
1723
1724 return len;
1725}
1726
1727/*
1728 * Begins an HTTP request on the multi handle. The caller should have set up all
1729 * request-specific options on actx->curl first. The server's response body will
1730 * be accumulated in actx->work_data (which will be reset, so don't store
1731 * anything important there across this call).
1732 *
1733 * Once a request is queued, it can be driven to completion via drive_request().
1734 * If actx->running is zero upon return, the request has already finished and
1735 * drive_request() can be called without returning control to the client.
1736 */
1737static bool
1739{
1740 CURLMcode err;
1741
1743 CHECK_SETOPT(actx, CURLOPT_WRITEFUNCTION, append_data, return false);
1744 CHECK_SETOPT(actx, CURLOPT_WRITEDATA, actx, return false);
1745
1746 err = curl_multi_add_handle(actx->curlm, actx->curl);
1747 if (err)
1748 {
1749 actx_error(actx, "failed to queue HTTP request: %s",
1750 curl_multi_strerror(err));
1751 return false;
1752 }
1753
1754 /*
1755 * actx->running tracks the number of running handles, so we can
1756 * immediately call back if no waiting is needed.
1757 *
1758 * Even though this is nominally an asynchronous process, there are some
1759 * operations that can synchronously fail by this point (e.g. connections
1760 * to closed local ports) or even synchronously succeed if the stars align
1761 * (all the libcurl connection caches hit and the server is fast).
1762 */
1763 err = curl_multi_socket_action(actx->curlm, CURL_SOCKET_TIMEOUT, 0, &actx->running);
1764 if (err)
1765 {
1766 actx_error(actx, "asynchronous HTTP request failed: %s",
1767 curl_multi_strerror(err));
1768 return false;
1769 }
1770
1771 return true;
1772}
1773
1774/*
1775 * CURL_IGNORE_DEPRECATION was added in 7.87.0. If it's not defined, we can make
1776 * it a no-op.
1777 */
1778#ifndef CURL_IGNORE_DEPRECATION
1779#define CURL_IGNORE_DEPRECATION(x) x
1780#endif
1781
1782/*
1783 * Drives the multi handle towards completion. The caller should have already
1784 * set up an asynchronous request via start_request().
1785 */
1788{
1789 CURLMcode err;
1790 CURLMsg *msg;
1791 int msgs_left;
1792 bool done;
1793
1794 if (actx->running)
1795 {
1796 /*---
1797 * There's an async request in progress. Pump the multi handle.
1798 *
1799 * curl_multi_socket_all() is officially deprecated, because it's
1800 * inefficient and pointless if your event loop has already handed you
1801 * the exact sockets that are ready. But that's not our use case --
1802 * our client has no way to tell us which sockets are ready. (They
1803 * don't even know there are sockets to begin with.)
1804 *
1805 * We can grab the list of triggered events from the multiplexer
1806 * ourselves, but that's effectively what curl_multi_socket_all() is
1807 * going to do. And there are currently no plans for the Curl project
1808 * to remove or break this API, so ignore the deprecation. See
1809 *
1810 * https://curl.se/mail/lib-2024-11/0028.html
1811 *
1812 */
1814 err = curl_multi_socket_all(actx->curlm, &actx->running);
1815 )
1816
1817 if (err)
1818 {
1819 actx_error(actx, "asynchronous HTTP request failed: %s",
1820 curl_multi_strerror(err));
1821 return PGRES_POLLING_FAILED;
1822 }
1823
1824 if (actx->running)
1825 {
1826 /* We'll come back again. */
1827 return PGRES_POLLING_READING;
1828 }
1829 }
1830
1831 done = false;
1832 while ((msg = curl_multi_info_read(actx->curlm, &msgs_left)) != NULL)
1833 {
1834 if (msg->msg != CURLMSG_DONE)
1835 {
1836 /*
1837 * Future libcurl versions may define new message types; we don't
1838 * know how to handle them, so we'll ignore them.
1839 */
1840 continue;
1841 }
1842
1843 /* First check the status of the request itself. */
1844 if (msg->data.result != CURLE_OK)
1845 {
1846 /*
1847 * If a more specific error hasn't already been reported, use
1848 * libcurl's description.
1849 */
1850 if (actx->errbuf.len == 0)
1851 actx_error_str(actx, curl_easy_strerror(msg->data.result));
1852
1853 return PGRES_POLLING_FAILED;
1854 }
1855
1856 /* Now remove the finished handle; we'll add it back later if needed. */
1857 err = curl_multi_remove_handle(actx->curlm, msg->easy_handle);
1858 if (err)
1859 {
1860 actx_error(actx, "libcurl easy handle removal failed: %s",
1861 curl_multi_strerror(err));
1862 return PGRES_POLLING_FAILED;
1863 }
1864
1865 done = true;
1866 }
1867
1868 /* Sanity check. */
1869 if (!done)
1870 {
1871 actx_error(actx, "no result was retrieved for the finished handle");
1872 return PGRES_POLLING_FAILED;
1873 }
1874
1875 return PGRES_POLLING_OK;
1876}
1877
1878/*
1879 * URL-Encoding Helpers
1880 */
1881
1882/*
1883 * Encodes a string using the application/x-www-form-urlencoded format, and
1884 * appends it to the given buffer.
1885 */
1886static void
1888{
1889 char *escaped;
1890 char *haystack;
1891 char *match;
1892
1893 /* The first parameter to curl_easy_escape is deprecated by Curl */
1894 escaped = curl_easy_escape(NULL, s, 0);
1895 if (!escaped)
1896 {
1897 termPQExpBuffer(buf); /* mark the buffer broken */
1898 return;
1899 }
1900
1901 /*
1902 * curl_easy_escape() almost does what we want, but we need the
1903 * query-specific flavor which uses '+' instead of '%20' for spaces. The
1904 * Curl command-line tool does this with a simple search-and-replace, so
1905 * follow its lead.
1906 */
1907 haystack = escaped;
1908
1909 while ((match = strstr(haystack, "%20")) != NULL)
1910 {
1911 /* Append the unmatched portion, followed by the plus sign. */
1912 appendBinaryPQExpBuffer(buf, haystack, match - haystack);
1914
1915 /* Keep searching after the match. */
1916 haystack = match + 3 /* strlen("%20") */ ;
1917 }
1918
1919 /* Push the remainder of the string onto the buffer. */
1920 appendPQExpBufferStr(buf, haystack);
1921
1922 curl_free(escaped);
1923}
1924
1925/*
1926 * Convenience wrapper for encoding a single string. Returns NULL on allocation
1927 * failure.
1928 */
1929static char *
1930urlencode(const char *s)
1931{
1933
1936
1937 return PQExpBufferDataBroken(buf) ? NULL : buf.data;
1938}
1939
1940/*
1941 * Appends a key/value pair to the end of an application/x-www-form-urlencoded
1942 * list.
1943 */
1944static void
1945build_urlencoded(PQExpBuffer buf, const char *key, const char *value)
1946{
1947 if (buf->len)
1949
1953}
1954
1955/*
1956 * Specific HTTP Request Handlers
1957 *
1958 * This is finally the beginning of the actual application logic. Generally
1959 * speaking, a single request consists of a start_* and a finish_* step, with
1960 * drive_request() pumping the machine in between.
1961 */
1962
1963/*
1964 * Queue an OpenID Provider Configuration Request:
1965 *
1966 * https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
1967 * https://www.rfc-editor.org/rfc/rfc8414#section-3.1
1968 *
1969 * This is done first to get the endpoint URIs we need to contact and to make
1970 * sure the provider provides a device authorization flow. finish_discovery()
1971 * will fill in actx->provider.
1972 */
1973static bool
1974start_discovery(struct async_ctx *actx, const char *discovery_uri)
1975{
1976 CHECK_SETOPT(actx, CURLOPT_HTTPGET, 1L, return false);
1977 CHECK_SETOPT(actx, CURLOPT_URL, discovery_uri, return false);
1978
1979 return start_request(actx);
1980}
1981
1982static bool
1984{
1985 long response_code;
1986
1987 /*----
1988 * Now check the response. OIDC Discovery 1.0 is pretty strict:
1989 *
1990 * A successful response MUST use the 200 OK HTTP status code and
1991 * return a JSON object using the application/json content type that
1992 * contains a set of Claims as its members that are a subset of the
1993 * Metadata values defined in Section 3.
1994 *
1995 * Compared to standard HTTP semantics, this makes life easy -- we don't
1996 * need to worry about redirections (which would call the Issuer host
1997 * validation into question), or non-authoritative responses, or any other
1998 * complications.
1999 */
2000 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2001
2002 if (response_code != 200)
2003 {
2004 actx_error(actx, "unexpected response code %ld", response_code);
2005 return false;
2006 }
2007
2008 /*
2009 * Pull the fields we care about from the document.
2010 */
2011 actx->errctx = "failed to parse OpenID discovery document";
2012 if (!parse_provider(actx, &actx->provider))
2013 return false; /* error message already set */
2014
2015 /*
2016 * Fill in any defaults for OPTIONAL/RECOMMENDED fields we care about.
2017 */
2019 {
2020 /*
2021 * Per Section 3, the default is ["authorization_code", "implicit"].
2022 */
2023 struct curl_slist *temp = actx->provider.grant_types_supported;
2024
2025 temp = curl_slist_append(temp, "authorization_code");
2026 if (temp)
2027 {
2028 temp = curl_slist_append(temp, "implicit");
2029 }
2030
2031 if (!temp)
2032 {
2033 actx_error(actx, "out of memory");
2034 return false;
2035 }
2036
2037 actx->provider.grant_types_supported = temp;
2038 }
2039
2040 return true;
2041}
2042
2043/*
2044 * Ensure that the discovery document is provided by the expected issuer.
2045 * Currently, issuers are statically configured in the connection string.
2046 */
2047static bool
2049{
2050 const struct provider *provider = &actx->provider;
2051
2052 Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
2053 Assert(provider->issuer); /* ensured by parse_provider() */
2054
2055 /*---
2056 * We require strict equality for issuer identifiers -- no path or case
2057 * normalization, no substitution of default ports and schemes, etc. This
2058 * is done to match the rules in OIDC Discovery Sec. 4.3 for config
2059 * validation:
2060 *
2061 * The issuer value returned MUST be identical to the Issuer URL that
2062 * was used as the prefix to /.well-known/openid-configuration to
2063 * retrieve the configuration information.
2064 *
2065 * as well as the rules set out in RFC 9207 for avoiding mix-up attacks:
2066 *
2067 * Clients MUST then [...] compare the result to the issuer identifier
2068 * of the authorization server where the authorization request was
2069 * sent to. This comparison MUST use simple string comparison as defined
2070 * in Section 6.2.1 of [RFC3986].
2071 */
2072 if (strcmp(conn->oauth_issuer_id, provider->issuer) != 0)
2073 {
2074 actx_error(actx,
2075 "the issuer identifier (%s) does not match oauth_issuer (%s)",
2077 return false;
2078 }
2079
2080 return true;
2081}
2082
2083#define HTTPS_SCHEME "https://"
2084#define OAUTH_GRANT_TYPE_DEVICE_CODE "urn:ietf:params:oauth:grant-type:device_code"
2085
2086/*
2087 * Ensure that the provider supports the Device Authorization flow (i.e. it
2088 * provides an authorization endpoint, and both the token and authorization
2089 * endpoint URLs seem reasonable).
2090 */
2091static bool
2093{
2094 const struct provider *provider = &actx->provider;
2095
2096 Assert(provider->issuer); /* ensured by parse_provider() */
2097 Assert(provider->token_endpoint); /* ensured by parse_provider() */
2098
2100 {
2101 actx_error(actx,
2102 "issuer \"%s\" does not provide a device authorization endpoint",
2103 provider->issuer);
2104 return false;
2105 }
2106
2107 /*
2108 * The original implementation checked that OAUTH_GRANT_TYPE_DEVICE_CODE
2109 * was present in the discovery document's grant_types_supported list. MS
2110 * Entra does not advertise this grant type, though, and since it doesn't
2111 * make sense to stand up a device_authorization_endpoint without also
2112 * accepting device codes at the token_endpoint, that's the only thing we
2113 * currently require.
2114 */
2115
2116 /*
2117 * Although libcurl will fail later if the URL contains an unsupported
2118 * scheme, that error message is going to be a bit opaque. This is a
2119 * decent time to bail out if we're not using HTTPS for the endpoints
2120 * we'll use for the flow.
2121 */
2122 if (!actx->debugging)
2123 {
2125 HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
2126 {
2127 actx_error(actx,
2128 "device authorization endpoint \"%s\" must use HTTPS",
2130 return false;
2131 }
2132
2134 HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
2135 {
2136 actx_error(actx,
2137 "token endpoint \"%s\" must use HTTPS",
2139 return false;
2140 }
2141 }
2142
2143 return true;
2144}
2145
2146/*
2147 * Adds the client ID (and secret, if provided) to the current request, using
2148 * either HTTP headers or the request body.
2149 */
2150static bool
2152{
2153 bool success = false;
2154 char *username = NULL;
2155 char *password = NULL;
2156
2157 if (conn->oauth_client_secret) /* Zero-length secrets are permitted! */
2158 {
2159 /*----
2160 * Use HTTP Basic auth to send the client_id and secret. Per RFC 6749,
2161 * Sec. 2.3.1,
2162 *
2163 * Including the client credentials in the request-body using the
2164 * two parameters is NOT RECOMMENDED and SHOULD be limited to
2165 * clients unable to directly utilize the HTTP Basic authentication
2166 * scheme (or other password-based HTTP authentication schemes).
2167 *
2168 * Additionally:
2169 *
2170 * The client identifier is encoded using the
2171 * "application/x-www-form-urlencoded" encoding algorithm per Appendix
2172 * B, and the encoded value is used as the username; the client
2173 * password is encoded using the same algorithm and used as the
2174 * password.
2175 *
2176 * (Appendix B modifies application/x-www-form-urlencoded by requiring
2177 * an initial UTF-8 encoding step. Since the client ID and secret must
2178 * both be 7-bit ASCII -- RFC 6749 Appendix A -- we don't worry about
2179 * that in this function.)
2180 *
2181 * client_id is not added to the request body in this case. Not only
2182 * would it be redundant, but some providers in the wild (e.g. Okta)
2183 * refuse to accept it.
2184 */
2187
2188 if (!username || !password)
2189 {
2190 actx_error(actx, "out of memory");
2191 goto cleanup;
2192 }
2193
2194 CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_BASIC, goto cleanup);
2195 CHECK_SETOPT(actx, CURLOPT_USERNAME, username, goto cleanup);
2196 CHECK_SETOPT(actx, CURLOPT_PASSWORD, password, goto cleanup);
2197
2198 actx->used_basic_auth = true;
2199 }
2200 else
2201 {
2202 /*
2203 * If we're not otherwise authenticating, client_id is REQUIRED in the
2204 * request body.
2205 */
2206 build_urlencoded(reqbody, "client_id", conn->oauth_client_id);
2207
2208 CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, goto cleanup);
2209 actx->used_basic_auth = false;
2210 }
2211
2212 success = true;
2213
2214cleanup:
2215 free(username);
2216 free(password);
2217
2218 return success;
2219}
2220
2221/*
2222 * Queue a Device Authorization Request:
2223 *
2224 * https://www.rfc-editor.org/rfc/rfc8628#section-3.1
2225 *
2226 * This is the second step. We ask the provider to verify the end user out of
2227 * band and authorize us to act on their behalf; it will give us the required
2228 * nonces for us to later poll the request status, which we'll grab in
2229 * finish_device_authz().
2230 */
2231static bool
2233{
2234 const char *device_authz_uri = actx->provider.device_authorization_endpoint;
2235 PQExpBuffer work_buffer = &actx->work_data;
2236
2237 Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
2238 Assert(device_authz_uri); /* ensured by check_for_device_flow() */
2239
2240 /* Construct our request body. */
2241 resetPQExpBuffer(work_buffer);
2242 if (conn->oauth_scope && conn->oauth_scope[0])
2243 build_urlencoded(work_buffer, "scope", conn->oauth_scope);
2244
2245 if (!add_client_identification(actx, work_buffer, conn))
2246 return false;
2247
2248 if (PQExpBufferBroken(work_buffer))
2249 {
2250 actx_error(actx, "out of memory");
2251 return false;
2252 }
2253
2254 /* Make our request. */
2255 CHECK_SETOPT(actx, CURLOPT_URL, device_authz_uri, return false);
2256 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2257
2258 return start_request(actx);
2259}
2260
2261static bool
2263{
2264 long response_code;
2265
2266 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2267
2268 /*
2269 * Per RFC 8628, Section 3, a successful device authorization response
2270 * uses 200 OK.
2271 */
2272 if (response_code == 200)
2273 {
2274 actx->errctx = "failed to parse device authorization";
2275 if (!parse_device_authz(actx, &actx->authz))
2276 return false; /* error message already set */
2277
2278 return true;
2279 }
2280
2281 /*
2282 * The device authorization endpoint uses the same error response as the
2283 * token endpoint, so the error handling roughly follows
2284 * finish_token_request(). The key difference is that an error here is
2285 * immediately fatal.
2286 */
2287 if (response_code == 400 || response_code == 401)
2288 {
2289 struct token_error err = {0};
2290
2291 if (!parse_token_error(actx, &err))
2292 {
2294 return false;
2295 }
2296
2297 /* Copy the token error into the context error buffer */
2298 record_token_error(actx, &err);
2299
2301 return false;
2302 }
2303
2304 /* Any other response codes are considered invalid */
2305 actx_error(actx, "unexpected response code %ld", response_code);
2306 return false;
2307}
2308
2309/*
2310 * Queue an Access Token Request:
2311 *
2312 * https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3
2313 *
2314 * This is the final step. We continually poll the token endpoint to see if the
2315 * user has authorized us yet. finish_token_request() will pull either the token
2316 * or a (ideally temporary) error status from the provider.
2317 */
2318static bool
2320{
2321 const char *token_uri = actx->provider.token_endpoint;
2322 const char *device_code = actx->authz.device_code;
2323 PQExpBuffer work_buffer = &actx->work_data;
2324
2325 Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
2326 Assert(token_uri); /* ensured by parse_provider() */
2327 Assert(device_code); /* ensured by parse_device_authz() */
2328
2329 /* Construct our request body. */
2330 resetPQExpBuffer(work_buffer);
2331 build_urlencoded(work_buffer, "device_code", device_code);
2332 build_urlencoded(work_buffer, "grant_type", OAUTH_GRANT_TYPE_DEVICE_CODE);
2333
2334 if (!add_client_identification(actx, work_buffer, conn))
2335 return false;
2336
2337 if (PQExpBufferBroken(work_buffer))
2338 {
2339 actx_error(actx, "out of memory");
2340 return false;
2341 }
2342
2343 /* Make our request. */
2344 CHECK_SETOPT(actx, CURLOPT_URL, token_uri, return false);
2345 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2346
2347 return start_request(actx);
2348}
2349
2350static bool
2351finish_token_request(struct async_ctx *actx, struct token *tok)
2352{
2353 long response_code;
2354
2355 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2356
2357 /*
2358 * Per RFC 6749, Section 5, a successful response uses 200 OK.
2359 */
2360 if (response_code == 200)
2361 {
2362 actx->errctx = "failed to parse access token response";
2363 if (!parse_access_token(actx, tok))
2364 return false; /* error message already set */
2365
2366 return true;
2367 }
2368
2369 /*
2370 * An error response uses either 400 Bad Request or 401 Unauthorized.
2371 * There are references online to implementations using 403 for error
2372 * return which would violate the specification. For now we stick to the
2373 * specification but we might have to revisit this.
2374 */
2375 if (response_code == 400 || response_code == 401)
2376 {
2377 if (!parse_token_error(actx, &tok->err))
2378 return false;
2379
2380 return true;
2381 }
2382
2383 /* Any other response codes are considered invalid */
2384 actx_error(actx, "unexpected response code %ld", response_code);
2385 return false;
2386}
2387
2388/*
2389 * Finishes the token request and examines the response. If the flow has
2390 * completed, a valid token will be returned via the parameter list. Otherwise,
2391 * the token parameter remains unchanged, and the caller needs to wait for
2392 * another interval (which will have been increased in response to a slow_down
2393 * message from the server) before starting a new token request.
2394 *
2395 * False is returned only for permanent error conditions.
2396 */
2397static bool
2399{
2400 bool success = false;
2401 struct token tok = {0};
2402 const struct token_error *err;
2403
2404 if (!finish_token_request(actx, &tok))
2405 goto token_cleanup;
2406
2407 /* A successful token request gives either a token or an in-band error. */
2408 Assert(tok.access_token || tok.err.error);
2409
2410 if (tok.access_token)
2411 {
2412 *token = tok.access_token;
2413 tok.access_token = NULL;
2414
2415 success = true;
2416 goto token_cleanup;
2417 }
2418
2419 /*
2420 * authorization_pending and slow_down are the only acceptable errors;
2421 * anything else and we bail. These are defined in RFC 8628, Sec. 3.5.
2422 */
2423 err = &tok.err;
2424 if (strcmp(err->error, "authorization_pending") != 0 &&
2425 strcmp(err->error, "slow_down") != 0)
2426 {
2427 record_token_error(actx, err);
2428 goto token_cleanup;
2429 }
2430
2431 /*
2432 * A slow_down error requires us to permanently increase our retry
2433 * interval by five seconds.
2434 */
2435 if (strcmp(err->error, "slow_down") == 0)
2436 {
2437 int prev_interval = actx->authz.interval;
2438
2439 actx->authz.interval += 5;
2440 if (actx->authz.interval < prev_interval)
2441 {
2442 actx_error(actx, "slow_down interval overflow");
2443 goto token_cleanup;
2444 }
2445 }
2446
2447 success = true;
2448
2449token_cleanup:
2450 free_token(&tok);
2451 return success;
2452}
2453
2454/*
2455 * Displays a device authorization prompt for action by the end user, either via
2456 * the PQauthDataHook, or by a message on standard error if no hook is set.
2457 */
2458static bool
2460{
2461 int res;
2462 PGpromptOAuthDevice prompt = {
2464 .user_code = actx->authz.user_code,
2465 .verification_uri_complete = actx->authz.verification_uri_complete,
2466 .expires_in = actx->authz.expires_in,
2467 };
2468
2470
2471 if (!res)
2472 {
2473 /*
2474 * translator: The first %s is a URL for the user to visit in a
2475 * browser, and the second %s is a code to be copy-pasted there.
2476 */
2477 fprintf(stderr, libpq_gettext("Visit %s and enter the code: %s\n"),
2478 prompt.verification_uri, prompt.user_code);
2479 }
2480 else if (res < 0)
2481 {
2482 actx_error(actx, "device prompt failed");
2483 return false;
2484 }
2485
2486 return true;
2487}
2488
2489/*
2490 * Calls curl_global_init() in a thread-safe way.
2491 *
2492 * libcurl has stringent requirements for the thread context in which you call
2493 * curl_global_init(), because it's going to try initializing a bunch of other
2494 * libraries (OpenSSL, Winsock, etc). Recent versions of libcurl have improved
2495 * the thread-safety situation, but there's a chicken-and-egg problem at
2496 * runtime: you can't check the thread safety until you've initialized libcurl,
2497 * which you can't do from within a thread unless you know it's thread-safe...
2498 *
2499 * Returns true if initialization was successful. Successful or not, this
2500 * function will not try to reinitialize Curl on successive calls.
2501 */
2502static bool
2504{
2505 /*
2506 * Don't let the compiler play tricks with this variable. In the
2507 * HAVE_THREADSAFE_CURL_GLOBAL_INIT case, we don't care if two threads
2508 * enter simultaneously, but we do care if this gets set transiently to
2509 * PG_BOOL_YES/NO in cases where that's not the final answer.
2510 */
2511 static volatile PGTernaryBool init_successful = PG_BOOL_UNKNOWN;
2512#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
2513 curl_version_info_data *info;
2514#endif
2515
2516#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2517
2518 /*
2519 * Lock around the whole function. If a libpq client performs its own work
2520 * with libcurl, it must either ensure that Curl is initialized safely
2521 * before calling us (in which case our call will be a no-op), or else it
2522 * must guard its own calls to curl_global_init() with a registered
2523 * threadlock handler. See PQregisterThreadLock().
2524 */
2525 pglock_thread();
2526#endif
2527
2528 /*
2529 * Skip initialization if we've already done it. (Curl tracks the number
2530 * of calls; there's no point in incrementing the counter every time we
2531 * connect.)
2532 */
2533 if (init_successful == PG_BOOL_YES)
2534 goto done;
2535 else if (init_successful == PG_BOOL_NO)
2536 {
2538 "curl_global_init previously failed during OAuth setup");
2539 goto done;
2540 }
2541
2542 /*
2543 * We know we've already initialized Winsock by this point (see
2544 * pqMakeEmptyPGconn()), so we should be able to safely skip that bit. But
2545 * we have to tell libcurl to initialize everything else, because other
2546 * pieces of our client executable may already be using libcurl for their
2547 * own purposes. If we initialize libcurl with only a subset of its
2548 * features, we could break those other clients nondeterministically, and
2549 * that would probably be a nightmare to debug.
2550 *
2551 * If some other part of the program has already called this, it's a
2552 * no-op.
2553 */
2554 if (curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32) != CURLE_OK)
2555 {
2557 "curl_global_init failed during OAuth setup");
2558 init_successful = PG_BOOL_NO;
2559 goto done;
2560 }
2561
2562#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
2563
2564 /*
2565 * If we determined at configure time that the Curl installation is
2566 * thread-safe, our job here is much easier. We simply initialize above
2567 * without any locking (concurrent or duplicated calls are fine in that
2568 * situation), then double-check to make sure the runtime setting agrees,
2569 * to try to catch silent downgrades.
2570 */
2571 info = curl_version_info(CURLVERSION_NOW);
2572 if (!(info->features & CURL_VERSION_THREADSAFE))
2573 {
2574 /*
2575 * In a downgrade situation, the damage is already done. Curl global
2576 * state may be corrupted. Be noisy.
2577 */
2578 libpq_append_conn_error(conn, "libcurl is no longer thread-safe\n"
2579 "\tCurl initialization was reported thread-safe when libpq\n"
2580 "\twas compiled, but the currently installed version of\n"
2581 "\tlibcurl reports that it is not. Recompile libpq against\n"
2582 "\tthe installed version of libcurl.");
2583 init_successful = PG_BOOL_NO;
2584 goto done;
2585 }
2586#endif
2587
2588 init_successful = PG_BOOL_YES;
2589
2590done:
2591#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2593#endif
2594 return (init_successful == PG_BOOL_YES);
2595}
2596
2597/*
2598 * The core nonblocking libcurl implementation. This will be called several
2599 * times to pump the async engine.
2600 *
2601 * The architecture is based on PQconnectPoll(). The first half drives the
2602 * connection state forward as necessary, returning if we're not ready to
2603 * proceed to the next step yet. The second half performs the actual transition
2604 * between states.
2605 *
2606 * You can trace the overall OAuth flow through the second half. It's linear
2607 * until we get to the end, where we flip back and forth between
2608 * OAUTH_STEP_TOKEN_REQUEST and OAUTH_STEP_WAIT_INTERVAL to regularly ping the
2609 * provider.
2610 */
2613{
2615 struct async_ctx *actx;
2616
2617 if (!initialize_curl(conn))
2618 return PGRES_POLLING_FAILED;
2619
2620 if (!state->async_ctx)
2621 {
2622 /*
2623 * Create our asynchronous state, and hook it into the upper-level
2624 * OAuth state immediately, so any failures below won't leak the
2625 * context allocation.
2626 */
2627 actx = calloc(1, sizeof(*actx));
2628 if (!actx)
2629 {
2630 libpq_append_conn_error(conn, "out of memory");
2631 return PGRES_POLLING_FAILED;
2632 }
2633
2634 actx->mux = PGINVALID_SOCKET;
2635 actx->timerfd = -1;
2636
2637 /* Should we enable unsafe features? */
2639
2640 state->async_ctx = actx;
2641
2642 initPQExpBuffer(&actx->work_data);
2643 initPQExpBuffer(&actx->errbuf);
2644
2645 if (!setup_multiplexer(actx))
2646 goto error_return;
2647
2648 if (!setup_curl_handles(actx))
2649 goto error_return;
2650 }
2651
2652 actx = state->async_ctx;
2653
2654 do
2655 {
2656 /* By default, the multiplexer is the altsock. Reassign as desired. */
2657 conn->altsock = actx->mux;
2658
2659 switch (actx->step)
2660 {
2661 case OAUTH_STEP_INIT:
2662 break;
2663
2667 {
2669
2670 status = drive_request(actx);
2671
2672 if (status == PGRES_POLLING_FAILED)
2673 goto error_return;
2674 else if (status != PGRES_POLLING_OK)
2675 {
2676 /* not done yet */
2677 return status;
2678 }
2679
2680 break;
2681 }
2682
2684
2685 /*
2686 * The client application is supposed to wait until our timer
2687 * expires before calling PQconnectPoll() again, but that
2688 * might not happen. To avoid sending a token request early,
2689 * check the timer before continuing.
2690 */
2691 if (!timer_expired(actx))
2692 {
2693 conn->altsock = actx->timerfd;
2694 return PGRES_POLLING_READING;
2695 }
2696
2697 /* Disable the expired timer. */
2698 if (!set_timer(actx, -1))
2699 goto error_return;
2700
2701 break;
2702 }
2703
2704 /*
2705 * Each case here must ensure that actx->running is set while we're
2706 * waiting on some asynchronous work. Most cases rely on
2707 * start_request() to do that for them.
2708 */
2709 switch (actx->step)
2710 {
2711 case OAUTH_STEP_INIT:
2712 actx->errctx = "failed to fetch OpenID discovery document";
2714 goto error_return;
2715
2716 actx->step = OAUTH_STEP_DISCOVERY;
2717 break;
2718
2720 if (!finish_discovery(actx))
2721 goto error_return;
2722
2723 if (!check_issuer(actx, conn))
2724 goto error_return;
2725
2726 actx->errctx = "cannot run OAuth device authorization";
2727 if (!check_for_device_flow(actx))
2728 goto error_return;
2729
2730 actx->errctx = "failed to obtain device authorization";
2731 if (!start_device_authz(actx, conn))
2732 goto error_return;
2733
2735 break;
2736
2738 if (!finish_device_authz(actx))
2739 goto error_return;
2740
2741 actx->errctx = "failed to obtain access token";
2742 if (!start_token_request(actx, conn))
2743 goto error_return;
2744
2746 break;
2747
2750 goto error_return;
2751
2752 if (!actx->user_prompted)
2753 {
2754 /*
2755 * Now that we know the token endpoint isn't broken, give
2756 * the user the login instructions.
2757 */
2758 if (!prompt_user(actx, conn))
2759 goto error_return;
2760
2761 actx->user_prompted = true;
2762 }
2763
2764 if (conn->oauth_token)
2765 break; /* done! */
2766
2767 /*
2768 * Wait for the required interval before issuing the next
2769 * request.
2770 */
2771 if (!set_timer(actx, actx->authz.interval * 1000))
2772 goto error_return;
2773
2774 /*
2775 * No Curl requests are running, so we can simplify by having
2776 * the client wait directly on the timerfd rather than the
2777 * multiplexer.
2778 */
2779 conn->altsock = actx->timerfd;
2780
2782 actx->running = 1;
2783 break;
2784
2786 actx->errctx = "failed to obtain access token";
2787 if (!start_token_request(actx, conn))
2788 goto error_return;
2789
2791 break;
2792 }
2793
2794 /*
2795 * The vast majority of the time, if we don't have a token at this
2796 * point, actx->running will be set. But there are some corner cases
2797 * where we can immediately loop back around; see start_request().
2798 */
2799 } while (!conn->oauth_token && !actx->running);
2800
2801 /* If we've stored a token, we're done. Otherwise come back later. */
2803
2804error_return:
2805
2806 /*
2807 * Assemble the three parts of our error: context, body, and detail. See
2808 * also the documentation for struct async_ctx.
2809 */
2810 if (actx->errctx)
2811 {
2813 libpq_gettext(actx->errctx));
2815 }
2816
2817 if (PQExpBufferDataBroken(actx->errbuf))
2819 libpq_gettext("out of memory"));
2820 else
2822
2823 if (actx->curl_err[0])
2824 {
2825 size_t len;
2826
2828 " (libcurl: %s)", actx->curl_err);
2829
2830 /* Sometimes libcurl adds a newline to the error buffer. :( */
2832 if (len >= 2 && conn->errorMessage.data[len - 2] == '\n')
2833 {
2834 conn->errorMessage.data[len - 2] = ')';
2835 conn->errorMessage.data[len - 1] = '\0';
2837 }
2838 }
2839
2841
2842 return PGRES_POLLING_FAILED;
2843}
2844
2845/*
2846 * The top-level entry point. This is a convenient place to put necessary
2847 * wrapper logic before handing off to the true implementation, above.
2848 */
2851{
2853#ifndef WIN32
2854 sigset_t osigset;
2855 bool sigpipe_pending;
2856 bool masked;
2857
2858 /*---
2859 * Ignore SIGPIPE on this thread during all Curl processing.
2860 *
2861 * Because we support multiple threads, we have to set up libcurl with
2862 * CURLOPT_NOSIGNAL, which disables its default global handling of
2863 * SIGPIPE. From the Curl docs:
2864 *
2865 * libcurl makes an effort to never cause such SIGPIPE signals to
2866 * trigger, but some operating systems have no way to avoid them and
2867 * even on those that have there are some corner cases when they may
2868 * still happen, contrary to our desire.
2869 *
2870 * Note that libcurl is also at the mercy of its DNS resolution and SSL
2871 * libraries; if any of them forget a MSG_NOSIGNAL then we're in trouble.
2872 * Modern platforms and libraries seem to get it right, so this is a
2873 * difficult corner case to exercise in practice, and unfortunately it's
2874 * not really clear whether it's necessary in all cases.
2875 */
2876 masked = (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0);
2877#endif
2878
2880
2881#ifndef WIN32
2882 if (masked)
2883 {
2884 /*
2885 * Undo the SIGPIPE mask. Assume we may have gotten EPIPE (we have no
2886 * way of knowing at this level).
2887 */
2888 pq_reset_sigpipe(&osigset, sigpipe_pending, true /* EPIPE, maybe */ );
2889 }
2890#endif
2891
2892 return result;
2893}
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 @162 value
static bool success
Definition: initdb.c:186
static char * username
Definition: initdb.c:153
#define close(a)
Definition: win32.h:12
int i
Definition: isn.c:72
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:113
@ PGRES_POLLING_OK
Definition: libpq-fe.h:117
@ PGRES_POLLING_READING
Definition: libpq-fe.h:115
@ PGRES_POLLING_FAILED
Definition: libpq-fe.h:114
@ PQAUTHDATA_PROMPT_OAUTH_DEVICE
Definition: libpq-fe.h:193
#define libpq_gettext(x)
Definition: libpq-int.h:934
PGTernaryBool
Definition: libpq-int.h:263
@ PG_BOOL_YES
Definition: libpq-int.h:265
@ PG_BOOL_NO
Definition: libpq-int.h:266
@ PG_BOOL_UNKNOWN
Definition: libpq-int.h:264
#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:734
const char * user_code
Definition: libpq-fe.h:735
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
union json_field::@185 target
const char * name
struct curl_slist ** array
JsonTokenType type
const struct json_field * active
const struct json_field * fields
PQExpBuffer errbuf
char * oauth_discovery_uri
Definition: libpq-int.h:443
char * oauth_scope
Definition: libpq-int.h:447
char * oauth_client_id
Definition: libpq-int.h:445
char * oauth_client_secret
Definition: libpq-int.h:446
char * oauth_token
Definition: libpq-int.h:448
char * oauth_issuer_id
Definition: libpq-int.h:442
PQExpBufferData errorMessage
Definition: libpq-int.h:671
pgsocket altsock
Definition: libpq-int.h:530
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