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