Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * fe-auth-oauth.c
4 : * The front-end (client) implementation of OAuth/OIDC authentication
5 : * using the SASL OAUTHBEARER mechanism.
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/fe-auth-oauth.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 :
16 : #include "postgres_fe.h"
17 :
18 : #ifdef USE_DYNAMIC_OAUTH
19 : #include <dlfcn.h>
20 : #endif
21 :
22 : #include "common/base64.h"
23 : #include "common/hmac.h"
24 : #include "common/jsonapi.h"
25 : #include "common/oauth-common.h"
26 : #include "fe-auth.h"
27 : #include "fe-auth-oauth.h"
28 : #include "mb/pg_wchar.h"
29 : #include "pg_config_paths.h"
30 :
31 : /* The exported OAuth callback mechanism. */
32 : static void *oauth_init(PGconn *conn, const char *password,
33 : const char *sasl_mechanism);
34 : static SASLStatus oauth_exchange(void *opaq, bool final,
35 : char *input, int inputlen,
36 : char **output, int *outputlen);
37 : static bool oauth_channel_bound(void *opaq);
38 : static void oauth_free(void *opaq);
39 :
40 : const pg_fe_sasl_mech pg_oauth_mech = {
41 : oauth_init,
42 : oauth_exchange,
43 : oauth_channel_bound,
44 : oauth_free,
45 : };
46 :
47 : /*
48 : * Initializes mechanism state for OAUTHBEARER.
49 : *
50 : * For a full description of the API, see libpq/fe-auth-sasl.h.
51 : */
52 : static void *
53 0 : oauth_init(PGconn *conn, const char *password,
54 : const char *sasl_mechanism)
55 : {
56 : fe_oauth_state *state;
57 :
58 : /*
59 : * We only support one SASL mechanism here; anything else is programmer
60 : * error.
61 : */
62 : Assert(sasl_mechanism != NULL);
63 : Assert(strcmp(sasl_mechanism, OAUTHBEARER_NAME) == 0);
64 :
65 0 : state = calloc(1, sizeof(*state));
66 0 : if (!state)
67 0 : return NULL;
68 :
69 0 : state->step = FE_OAUTH_INIT;
70 0 : state->conn = conn;
71 :
72 0 : return state;
73 : }
74 :
75 : /*
76 : * Frees the state allocated by oauth_init().
77 : *
78 : * This handles only mechanism state tied to the connection lifetime; state
79 : * stored in state->async_ctx is freed up either immediately after the
80 : * authentication handshake succeeds, or before the mechanism is cleaned up on
81 : * failure. See pg_fe_cleanup_oauth_flow() and cleanup_oauth_flow().
82 : */
83 : static void
84 0 : oauth_free(void *opaq)
85 : {
86 0 : fe_oauth_state *state = opaq;
87 :
88 : /* Any async authentication state should have been cleaned up already. */
89 : Assert(!state->async_ctx);
90 :
91 0 : free(state);
92 0 : }
93 :
94 : #define kvsep "\x01"
95 :
96 : /*
97 : * Constructs an OAUTHBEARER client initial response (RFC 7628, Sec. 3.1).
98 : *
99 : * If discover is true, the initial response will contain a request for the
100 : * server's required OAuth parameters (Sec. 4.3). Otherwise, conn->token must
101 : * be set; it will be sent as the connection's bearer token.
102 : *
103 : * Returns the response as a null-terminated string, or NULL on error.
104 : */
105 : static char *
106 0 : client_initial_response(PGconn *conn, bool discover)
107 : {
108 : static const char *const resp_format = "n,," kvsep "auth=%s%s" kvsep kvsep;
109 :
110 : PQExpBufferData buf;
111 : const char *authn_scheme;
112 0 : char *response = NULL;
113 0 : const char *token = conn->oauth_token;
114 :
115 0 : if (discover)
116 : {
117 : /* Parameter discovery uses a completely empty auth value. */
118 0 : authn_scheme = token = "";
119 : }
120 : else
121 : {
122 : /*
123 : * Use a Bearer authentication scheme (RFC 6750, Sec. 2.1). A trailing
124 : * space is used as a separator.
125 : */
126 0 : authn_scheme = "Bearer ";
127 :
128 : /* conn->token must have been set in this case. */
129 0 : if (!token)
130 : {
131 : Assert(false);
132 0 : libpq_append_conn_error(conn,
133 : "internal error: no OAuth token was set for the connection");
134 0 : return NULL;
135 : }
136 : }
137 :
138 0 : initPQExpBuffer(&buf);
139 0 : appendPQExpBuffer(&buf, resp_format, authn_scheme, token);
140 :
141 0 : if (!PQExpBufferDataBroken(buf))
142 0 : response = strdup(buf.data);
143 0 : termPQExpBuffer(&buf);
144 :
145 0 : if (!response)
146 0 : libpq_append_conn_error(conn, "out of memory");
147 :
148 0 : return response;
149 : }
150 :
151 : /*
152 : * JSON Parser (for the OAUTHBEARER error result)
153 : */
154 :
155 : /* Relevant JSON fields in the error result object. */
156 : #define ERROR_STATUS_FIELD "status"
157 : #define ERROR_SCOPE_FIELD "scope"
158 : #define ERROR_OPENID_CONFIGURATION_FIELD "openid-configuration"
159 :
160 : /*
161 : * Limit the maximum number of nested objects/arrays. Because OAUTHBEARER
162 : * doesn't have any defined extensions for its JSON yet, we can be much more
163 : * conservative here than with libpq-oauth's MAX_OAUTH_NESTING_LEVEL; we expect
164 : * a nesting level of 1 in practice.
165 : */
166 : #define MAX_SASL_NESTING_LEVEL 8
167 :
168 : struct json_ctx
169 : {
170 : char *errmsg; /* any non-NULL value stops all processing */
171 : PQExpBufferData errbuf; /* backing memory for errmsg */
172 : int nested; /* nesting level (zero is the top) */
173 :
174 : const char *target_field_name; /* points to a static allocation */
175 : char **target_field; /* see below */
176 :
177 : /* target_field, if set, points to one of the following: */
178 : char *status;
179 : char *scope;
180 : char *discovery_uri;
181 : };
182 :
183 : #define oauth_json_has_error(ctx) \
184 : (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)
185 :
186 : #define oauth_json_set_error(ctx, fmt, ...) \
187 : do { \
188 : appendPQExpBuffer(&(ctx)->errbuf, libpq_gettext(fmt), ##__VA_ARGS__); \
189 : (ctx)->errmsg = (ctx)->errbuf.data; \
190 : } while (0)
191 :
192 : /* An untranslated version of oauth_json_set_error(). */
193 : #define oauth_json_set_error_internal(ctx, ...) \
194 : do { \
195 : appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
196 : (ctx)->errmsg = (ctx)->errbuf.data; \
197 : } while (0)
198 :
199 : static JsonParseErrorType
200 0 : oauth_json_object_start(void *state)
201 : {
202 0 : struct json_ctx *ctx = state;
203 :
204 0 : if (ctx->target_field)
205 : {
206 : Assert(ctx->nested == 1);
207 :
208 0 : oauth_json_set_error(ctx,
209 : "field \"%s\" must be a string",
210 : ctx->target_field_name);
211 : }
212 :
213 0 : ++ctx->nested;
214 0 : if (ctx->nested > MAX_SASL_NESTING_LEVEL)
215 0 : oauth_json_set_error(ctx, "JSON is too deeply nested");
216 :
217 0 : return oauth_json_has_error(ctx) ? JSON_SEM_ACTION_FAILED : JSON_SUCCESS;
218 : }
219 :
220 : static JsonParseErrorType
221 0 : oauth_json_object_end(void *state)
222 : {
223 0 : struct json_ctx *ctx = state;
224 :
225 0 : --ctx->nested;
226 0 : return JSON_SUCCESS;
227 : }
228 :
229 : static JsonParseErrorType
230 0 : oauth_json_object_field_start(void *state, char *name, bool isnull)
231 : {
232 0 : struct json_ctx *ctx = state;
233 :
234 : /* Only top-level keys are considered. */
235 0 : if (ctx->nested == 1)
236 : {
237 0 : if (strcmp(name, ERROR_STATUS_FIELD) == 0)
238 : {
239 0 : ctx->target_field_name = ERROR_STATUS_FIELD;
240 0 : ctx->target_field = &ctx->status;
241 : }
242 0 : else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
243 : {
244 0 : ctx->target_field_name = ERROR_SCOPE_FIELD;
245 0 : ctx->target_field = &ctx->scope;
246 : }
247 0 : else if (strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD) == 0)
248 : {
249 0 : ctx->target_field_name = ERROR_OPENID_CONFIGURATION_FIELD;
250 0 : ctx->target_field = &ctx->discovery_uri;
251 : }
252 : }
253 :
254 0 : return JSON_SUCCESS;
255 : }
256 :
257 : static JsonParseErrorType
258 0 : oauth_json_array_start(void *state)
259 : {
260 0 : struct json_ctx *ctx = state;
261 :
262 0 : if (!ctx->nested)
263 : {
264 0 : oauth_json_set_error(ctx, "top-level element must be an object");
265 : }
266 0 : else if (ctx->target_field)
267 : {
268 : Assert(ctx->nested == 1);
269 :
270 0 : oauth_json_set_error(ctx,
271 : "field \"%s\" must be a string",
272 : ctx->target_field_name);
273 : }
274 :
275 0 : ++ctx->nested;
276 0 : if (ctx->nested > MAX_SASL_NESTING_LEVEL)
277 0 : oauth_json_set_error(ctx, "JSON is too deeply nested");
278 :
279 0 : return oauth_json_has_error(ctx) ? JSON_SEM_ACTION_FAILED : JSON_SUCCESS;
280 : }
281 :
282 : static JsonParseErrorType
283 0 : oauth_json_array_end(void *state)
284 : {
285 0 : struct json_ctx *ctx = state;
286 :
287 0 : --ctx->nested;
288 0 : return JSON_SUCCESS;
289 : }
290 :
291 : static JsonParseErrorType
292 0 : oauth_json_scalar(void *state, char *token, JsonTokenType type)
293 : {
294 0 : struct json_ctx *ctx = state;
295 :
296 0 : if (!ctx->nested)
297 : {
298 0 : oauth_json_set_error(ctx, "top-level element must be an object");
299 0 : return JSON_SEM_ACTION_FAILED;
300 : }
301 :
302 0 : if (ctx->target_field)
303 : {
304 0 : if (ctx->nested != 1)
305 : {
306 : /*
307 : * ctx->target_field should not have been set for nested keys.
308 : * Assert and don't continue any further for production builds.
309 : */
310 : Assert(false);
311 0 : oauth_json_set_error_internal(ctx,
312 : "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
313 : ctx->nested);
314 0 : return JSON_SEM_ACTION_FAILED;
315 : }
316 :
317 : /*
318 : * We don't allow duplicate field names; error out if the target has
319 : * already been set.
320 : */
321 0 : if (*ctx->target_field)
322 : {
323 0 : oauth_json_set_error(ctx,
324 : "field \"%s\" is duplicated",
325 : ctx->target_field_name);
326 0 : return JSON_SEM_ACTION_FAILED;
327 : }
328 :
329 : /* The only fields we support are strings. */
330 0 : if (type != JSON_TOKEN_STRING)
331 : {
332 0 : oauth_json_set_error(ctx,
333 : "field \"%s\" must be a string",
334 : ctx->target_field_name);
335 0 : return JSON_SEM_ACTION_FAILED;
336 : }
337 :
338 0 : *ctx->target_field = strdup(token);
339 0 : if (!*ctx->target_field)
340 0 : return JSON_OUT_OF_MEMORY;
341 :
342 0 : ctx->target_field = NULL;
343 0 : ctx->target_field_name = NULL;
344 : }
345 : else
346 : {
347 : /* otherwise we just ignore it */
348 : }
349 :
350 0 : return JSON_SUCCESS;
351 : }
352 :
353 : #define HTTPS_SCHEME "https://"
354 : #define HTTP_SCHEME "http://"
355 :
356 : /* We support both well-known suffixes defined by RFC 8414. */
357 : #define WK_PREFIX "/.well-known/"
358 : #define OPENID_WK_SUFFIX "openid-configuration"
359 : #define OAUTH_WK_SUFFIX "oauth-authorization-server"
360 :
361 : /*
362 : * Derives an issuer identifier from one of our recognized .well-known URIs,
363 : * using the rules in RFC 8414.
364 : */
365 : static char *
366 0 : issuer_from_well_known_uri(PGconn *conn, const char *wkuri)
367 : {
368 0 : const char *authority_start = NULL;
369 : const char *wk_start;
370 : const char *wk_end;
371 : char *issuer;
372 : ptrdiff_t start_offset,
373 : end_offset;
374 : size_t end_len;
375 :
376 : /*
377 : * https:// is required for issuer identifiers (RFC 8414, Sec. 2; OIDC
378 : * Discovery 1.0, Sec. 3). This is a case-insensitive comparison at this
379 : * level (but issuer identifier comparison at the level above this is
380 : * case-sensitive, so in practice it's probably moot).
381 : */
382 0 : if (pg_strncasecmp(wkuri, HTTPS_SCHEME, strlen(HTTPS_SCHEME)) == 0)
383 0 : authority_start = wkuri + strlen(HTTPS_SCHEME);
384 :
385 0 : if (!authority_start
386 0 : && oauth_unsafe_debugging_enabled()
387 0 : && pg_strncasecmp(wkuri, HTTP_SCHEME, strlen(HTTP_SCHEME)) == 0)
388 : {
389 : /* Allow http:// for testing only. */
390 0 : authority_start = wkuri + strlen(HTTP_SCHEME);
391 : }
392 :
393 0 : if (!authority_start)
394 : {
395 0 : libpq_append_conn_error(conn,
396 : "OAuth discovery URI \"%s\" must use HTTPS",
397 : wkuri);
398 0 : return NULL;
399 : }
400 :
401 : /*
402 : * Well-known URIs in general may support queries and fragments, but the
403 : * two types we support here do not. (They must be constructed from the
404 : * components of issuer identifiers, which themselves may not contain any
405 : * queries or fragments.)
406 : *
407 : * It's important to check this first, to avoid getting tricked later by a
408 : * prefix buried inside a query or fragment.
409 : */
410 0 : if (strpbrk(authority_start, "?#") != NULL)
411 : {
412 0 : libpq_append_conn_error(conn,
413 : "OAuth discovery URI \"%s\" must not contain query or fragment components",
414 : wkuri);
415 0 : return NULL;
416 : }
417 :
418 : /*
419 : * Find the start of the .well-known prefix. IETF rules (RFC 8615) state
420 : * this must be at the beginning of the path component, but OIDC defined
421 : * it at the end instead (OIDC Discovery 1.0, Sec. 4), so we have to
422 : * search for it anywhere.
423 : */
424 0 : wk_start = strstr(authority_start, WK_PREFIX);
425 0 : if (!wk_start)
426 : {
427 0 : libpq_append_conn_error(conn,
428 : "OAuth discovery URI \"%s\" is not a .well-known URI",
429 : wkuri);
430 0 : return NULL;
431 : }
432 :
433 : /*
434 : * Now find the suffix type. We only support the two defined in OIDC
435 : * Discovery 1.0 and RFC 8414.
436 : */
437 0 : wk_end = wk_start + strlen(WK_PREFIX);
438 :
439 0 : if (strncmp(wk_end, OPENID_WK_SUFFIX, strlen(OPENID_WK_SUFFIX)) == 0)
440 0 : wk_end += strlen(OPENID_WK_SUFFIX);
441 0 : else if (strncmp(wk_end, OAUTH_WK_SUFFIX, strlen(OAUTH_WK_SUFFIX)) == 0)
442 0 : wk_end += strlen(OAUTH_WK_SUFFIX);
443 : else
444 0 : wk_end = NULL;
445 :
446 : /*
447 : * Even if there's a match, we still need to check to make sure the suffix
448 : * takes up the entire path segment, to weed out constructions like
449 : * "/.well-known/openid-configuration-bad".
450 : */
451 0 : if (!wk_end || (*wk_end != '/' && *wk_end != '\0'))
452 : {
453 0 : libpq_append_conn_error(conn,
454 : "OAuth discovery URI \"%s\" uses an unsupported .well-known suffix",
455 : wkuri);
456 0 : return NULL;
457 : }
458 :
459 : /*
460 : * Finally, make sure the .well-known components are provided either as a
461 : * prefix (IETF style) or as a postfix (OIDC style). In other words,
462 : * "https://localhost/a/.well-known/openid-configuration/b" is not allowed
463 : * to claim association with "https://localhost/a/b".
464 : */
465 0 : if (*wk_end != '\0')
466 : {
467 : /*
468 : * It's not at the end, so it's required to be at the beginning at the
469 : * path. Find the starting slash.
470 : */
471 : const char *path_start;
472 :
473 0 : path_start = strchr(authority_start, '/');
474 : Assert(path_start); /* otherwise we wouldn't have found WK_PREFIX */
475 :
476 0 : if (wk_start != path_start)
477 : {
478 0 : libpq_append_conn_error(conn,
479 : "OAuth discovery URI \"%s\" uses an invalid format",
480 : wkuri);
481 0 : return NULL;
482 : }
483 : }
484 :
485 : /* Checks passed! Now build the issuer. */
486 0 : issuer = strdup(wkuri);
487 0 : if (!issuer)
488 : {
489 0 : libpq_append_conn_error(conn, "out of memory");
490 0 : return NULL;
491 : }
492 :
493 : /*
494 : * The .well-known components are from [wk_start, wk_end). Remove those to
495 : * form the issuer ID, by shifting the path suffix (which may be empty)
496 : * leftwards.
497 : */
498 0 : start_offset = wk_start - wkuri;
499 0 : end_offset = wk_end - wkuri;
500 0 : end_len = strlen(wk_end) + 1; /* move the NULL terminator too */
501 :
502 0 : memmove(issuer + start_offset, issuer + end_offset, end_len);
503 :
504 0 : return issuer;
505 : }
506 :
507 : /*
508 : * Parses the server error result (RFC 7628, Sec. 3.2.2) contained in msg and
509 : * stores any discovered openid_configuration and scope settings for the
510 : * connection.
511 : */
512 : static bool
513 0 : handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
514 : {
515 : JsonLexContext *lex;
516 0 : JsonSemAction sem = {0};
517 : JsonParseErrorType err;
518 0 : struct json_ctx ctx = {0};
519 0 : char *errmsg = NULL;
520 0 : bool success = false;
521 :
522 : Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
523 :
524 : /* Sanity check. */
525 0 : if (strlen(msg) != msglen)
526 : {
527 0 : libpq_append_conn_error(conn,
528 : "server's error message contained an embedded NULL, and was discarded");
529 0 : return false;
530 : }
531 :
532 : /*
533 : * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
534 : * that up front.
535 : */
536 0 : if (pg_encoding_verifymbstr(PG_UTF8, msg, msglen) != msglen)
537 : {
538 0 : libpq_append_conn_error(conn,
539 : "server's error response is not valid UTF-8");
540 0 : return false;
541 : }
542 :
543 0 : lex = makeJsonLexContextCstringLen(NULL, msg, msglen, PG_UTF8, true);
544 0 : setJsonLexContextOwnsTokens(lex, true); /* must not leak on error */
545 :
546 0 : initPQExpBuffer(&ctx.errbuf);
547 0 : sem.semstate = &ctx;
548 :
549 0 : sem.object_start = oauth_json_object_start;
550 0 : sem.object_end = oauth_json_object_end;
551 0 : sem.object_field_start = oauth_json_object_field_start;
552 0 : sem.array_start = oauth_json_array_start;
553 0 : sem.array_end = oauth_json_array_end;
554 0 : sem.scalar = oauth_json_scalar;
555 :
556 0 : err = pg_parse_json(lex, &sem);
557 :
558 0 : if (err == JSON_SEM_ACTION_FAILED)
559 : {
560 0 : if (PQExpBufferDataBroken(ctx.errbuf))
561 0 : errmsg = libpq_gettext("out of memory");
562 0 : else if (ctx.errmsg)
563 0 : errmsg = ctx.errmsg;
564 : else
565 : {
566 : /*
567 : * Developer error: one of the action callbacks didn't call
568 : * oauth_json_set_error() before erroring out.
569 : */
570 : Assert(oauth_json_has_error(&ctx));
571 0 : errmsg = "<unexpected empty error>";
572 : }
573 : }
574 0 : else if (err != JSON_SUCCESS)
575 0 : errmsg = json_errdetail(err, lex);
576 :
577 0 : if (errmsg)
578 0 : libpq_append_conn_error(conn,
579 : "failed to parse server's error response: %s",
580 : errmsg);
581 :
582 : /* Don't need the error buffer or the JSON lexer anymore. */
583 0 : termPQExpBuffer(&ctx.errbuf);
584 0 : freeJsonLexContext(lex);
585 :
586 0 : if (errmsg)
587 0 : goto cleanup;
588 :
589 0 : if (ctx.discovery_uri)
590 : {
591 : char *discovery_issuer;
592 :
593 : /*
594 : * The URI MUST correspond to our existing issuer, to avoid mix-ups.
595 : *
596 : * Issuer comparison is done byte-wise, rather than performing any URL
597 : * normalization; this follows the suggestions for issuer comparison
598 : * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
599 : * vastly simplifies things. Since this is the key protection against
600 : * a rogue server sending the client to an untrustworthy location,
601 : * simpler is better.
602 : */
603 0 : discovery_issuer = issuer_from_well_known_uri(conn, ctx.discovery_uri);
604 0 : if (!discovery_issuer)
605 0 : goto cleanup; /* error message already set */
606 :
607 0 : if (strcmp(conn->oauth_issuer_id, discovery_issuer) != 0)
608 : {
609 0 : libpq_append_conn_error(conn,
610 : "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
611 : ctx.discovery_uri, discovery_issuer,
612 : conn->oauth_issuer_id);
613 :
614 0 : free(discovery_issuer);
615 0 : goto cleanup;
616 : }
617 :
618 0 : free(discovery_issuer);
619 :
620 0 : if (!conn->oauth_discovery_uri)
621 : {
622 0 : conn->oauth_discovery_uri = ctx.discovery_uri;
623 0 : ctx.discovery_uri = NULL;
624 : }
625 : else
626 : {
627 : /* This must match the URI we'd previously determined. */
628 0 : if (strcmp(conn->oauth_discovery_uri, ctx.discovery_uri) != 0)
629 : {
630 0 : libpq_append_conn_error(conn,
631 : "server's discovery document has moved to %s (previous location was %s)",
632 : ctx.discovery_uri,
633 : conn->oauth_discovery_uri);
634 0 : goto cleanup;
635 : }
636 : }
637 : }
638 :
639 0 : if (ctx.scope)
640 : {
641 : /* Servers may not override a previously set oauth_scope. */
642 0 : if (!conn->oauth_scope)
643 : {
644 0 : conn->oauth_scope = ctx.scope;
645 0 : ctx.scope = NULL;
646 : }
647 : }
648 :
649 0 : if (!ctx.status)
650 : {
651 0 : libpq_append_conn_error(conn,
652 : "server sent error response without a status");
653 0 : goto cleanup;
654 : }
655 :
656 0 : if (strcmp(ctx.status, "invalid_token") != 0)
657 : {
658 : /*
659 : * invalid_token is the only error code we'll automatically retry for;
660 : * otherwise, just bail out now.
661 : */
662 0 : libpq_append_conn_error(conn,
663 : "server rejected OAuth bearer token: %s",
664 : ctx.status);
665 0 : goto cleanup;
666 : }
667 :
668 0 : success = true;
669 :
670 0 : cleanup:
671 0 : free(ctx.status);
672 0 : free(ctx.scope);
673 0 : free(ctx.discovery_uri);
674 :
675 0 : return success;
676 : }
677 :
678 : /*
679 : * Helper for handling flow failures. If anything was put into request->error,
680 : * it's added to conn->errorMessage here.
681 : */
682 : static void
683 0 : report_flow_error(PGconn *conn, const PGoauthBearerRequestV2 *request)
684 : {
685 0 : fe_oauth_state *state = conn->sasl_state;
686 0 : const char *errmsg = request->error;
687 :
688 : /*
689 : * User-defined flows are called out explicitly so that the user knows who
690 : * to blame. Builtin flows don't need that extra message length; we expect
691 : * them to always fill in request->error on failure anyway.
692 : */
693 0 : if (state->builtin)
694 : {
695 0 : if (!errmsg)
696 : {
697 : /*
698 : * Don't turn a bug here into a crash in production, but don't
699 : * bother translating either.
700 : */
701 : Assert(false);
702 0 : errmsg = "builtin flow failed but did not provide an error message";
703 : }
704 :
705 0 : appendPQExpBufferStr(&conn->errorMessage, errmsg);
706 : }
707 : else
708 : {
709 0 : appendPQExpBufferStr(&conn->errorMessage,
710 0 : libpq_gettext("user-defined OAuth flow failed"));
711 0 : if (errmsg)
712 : {
713 0 : appendPQExpBufferStr(&conn->errorMessage, ": ");
714 0 : appendPQExpBufferStr(&conn->errorMessage, errmsg);
715 : }
716 : }
717 :
718 0 : appendPQExpBufferChar(&conn->errorMessage, '\n');
719 0 : }
720 :
721 : /*
722 : * Callback implementation of conn->async_auth() for OAuth flows. Delegates the
723 : * retrieval of the token to the PGoauthBearerRequestV2.async() callback.
724 : *
725 : * This will be called multiple times as needed; the callback is responsible for
726 : * setting an altsock to signal and returning the correct PGRES_POLLING_*
727 : * statuses for use by PQconnectPoll().
728 : */
729 : static PostgresPollingStatusType
730 0 : run_oauth_flow(PGconn *conn)
731 : {
732 0 : fe_oauth_state *state = conn->sasl_state;
733 0 : PGoauthBearerRequestV2 *request = state->async_ctx;
734 : PostgresPollingStatusType status;
735 :
736 0 : if (!request->v1.async)
737 : {
738 : Assert(!state->builtin); /* be very noisy if our code does this */
739 0 : libpq_append_conn_error(conn,
740 : "user-defined OAuth flow provided neither a token nor an async callback");
741 0 : return PGRES_POLLING_FAILED;
742 : }
743 :
744 0 : status = request->v1.async(conn,
745 : (PGoauthBearerRequest *) request,
746 0 : &conn->altsock);
747 :
748 0 : if (status == PGRES_POLLING_FAILED)
749 : {
750 0 : report_flow_error(conn, request);
751 0 : return status;
752 : }
753 0 : else if (status == PGRES_POLLING_OK)
754 : {
755 : /*
756 : * We already have a token, so copy it into the conn. (We can't hold
757 : * onto the original string, since it may not be safe for us to free()
758 : * it.)
759 : */
760 0 : if (!request->v1.token)
761 : {
762 : Assert(!state->builtin);
763 0 : libpq_append_conn_error(conn,
764 : "user-defined OAuth flow did not provide a token");
765 0 : return PGRES_POLLING_FAILED;
766 : }
767 :
768 0 : conn->oauth_token = strdup(request->v1.token);
769 0 : if (!conn->oauth_token)
770 : {
771 0 : libpq_append_conn_error(conn, "out of memory");
772 0 : return PGRES_POLLING_FAILED;
773 : }
774 :
775 0 : return PGRES_POLLING_OK;
776 : }
777 :
778 : /* The hook wants the client to poll the altsock. Make sure it set one. */
779 0 : if (conn->altsock == PGINVALID_SOCKET)
780 : {
781 : Assert(!state->builtin);
782 0 : libpq_append_conn_error(conn,
783 : "user-defined OAuth flow did not provide a socket for polling");
784 0 : return PGRES_POLLING_FAILED;
785 : }
786 :
787 0 : return status;
788 : }
789 :
790 : /*
791 : * Cleanup callback for the async flow. Delegates most of its job to
792 : * PGoauthBearerRequest.cleanup(), then disconnects the altsock and frees the
793 : * request itself.
794 : *
795 : * This is called either at the end of a successful authentication, or during
796 : * pqDropConnection(), so we won't leak resources even if PQconnectPoll() never
797 : * calls us back.
798 : */
799 : static void
800 0 : cleanup_oauth_flow(PGconn *conn)
801 : {
802 0 : fe_oauth_state *state = conn->sasl_state;
803 0 : PGoauthBearerRequestV2 *request = state->async_ctx;
804 :
805 : Assert(request);
806 :
807 0 : if (request->v1.cleanup)
808 0 : request->v1.cleanup(conn, (PGoauthBearerRequest *) request);
809 0 : conn->altsock = PGINVALID_SOCKET;
810 :
811 0 : free(request);
812 0 : state->async_ctx = NULL;
813 0 : }
814 :
815 : /*-------------
816 : * Builtin Flow
817 : *
818 : * There are three potential implementations of use_builtin_flow:
819 : *
820 : * 1) If the OAuth client is disabled at configuration time, return zero.
821 : * Dependent clients must provide their own flow.
822 : * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
823 : * the libpq-oauth plugin and use its implementation.
824 : * 3) Otherwise, use flow callbacks that are statically linked into the
825 : * executable.
826 : *
827 : * For caller convenience, the return value follows the convention of
828 : * PQauthDataHook: zero means no implementation is provided, negative indicates
829 : * failure, and positive indicates success.
830 : */
831 :
832 : #if !defined(USE_LIBCURL)
833 :
834 : /*
835 : * This configuration doesn't support the builtin flow.
836 : */
837 :
838 : static int
839 0 : use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
840 : {
841 0 : return 0;
842 : }
843 :
844 : #elif defined(USE_DYNAMIC_OAUTH)
845 :
846 : /*
847 : * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
848 : */
849 :
850 : typedef char *(*libpq_gettext_func) (const char *msgid);
851 :
852 : /*
853 : * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
854 : * callbacks into the connection's async auth handlers.
855 : *
856 : * Failure to load here results in a relatively quiet connection error, to
857 : * handle the use case where the build supports loading a flow but a user does
858 : * not want to install it. Troubleshooting of linker/loader failures can be done
859 : * via PGOAUTHDEBUG.
860 : *
861 : * The lifetime of *request ends shortly after this call, so it must be copied
862 : * to longer-lived storage.
863 : */
864 : static int
865 : use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
866 : {
867 : static bool initialized = false;
868 : static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
869 : int lockerr;
870 :
871 : void (*init) (libpq_gettext_func gettext_impl);
872 : int (*start_flow) (PGconn *conn, PGoauthBearerRequestV2 *request);
873 :
874 : /*
875 : * On macOS only, load the module using its absolute install path; the
876 : * standard search behavior is not very helpful for this use case. Unlike
877 : * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
878 : * absolute paths (modulo SIP effects), so tests can continue to work.
879 : *
880 : * On the other platforms, load the module using only the basename, to
881 : * rely on the runtime linker's standard search behavior.
882 : */
883 : const char *const module_name =
884 : #if defined(__darwin__)
885 : LIBDIR "/libpq-oauth" DLSUFFIX;
886 : #else
887 : "libpq-oauth" DLSUFFIX;
888 : #endif
889 :
890 : state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
891 : if (!state->builtin_flow)
892 : {
893 : /*
894 : * For end users, this probably isn't an error condition, it just
895 : * means the flow isn't installed. Developers and package maintainers
896 : * may want to debug this via the PGOAUTHDEBUG envvar, though.
897 : *
898 : * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
899 : */
900 : if (oauth_unsafe_debugging_enabled())
901 : fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
902 :
903 : return 0;
904 : }
905 :
906 : if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
907 : || (start_flow = dlsym(state->builtin_flow, "pg_start_oauthbearer")) == NULL)
908 : {
909 : /*
910 : * This is more of an error condition than the one above, but the
911 : * cause is still locked behind PGOAUTHDEBUG due to the dlerror()
912 : * threadsafety issue.
913 : */
914 : if (oauth_unsafe_debugging_enabled())
915 : fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
916 :
917 : dlclose(state->builtin_flow);
918 : state->builtin_flow = NULL;
919 :
920 : request->error = libpq_gettext("could not find entry point for libpq-oauth");
921 : return -1;
922 : }
923 :
924 : /*
925 : * Past this point, we do not unload the module. It stays in the process
926 : * permanently.
927 : */
928 :
929 : /*
930 : * We need to inject necessary function pointers into the module. This
931 : * only needs to be done once -- even if the pointers are constant,
932 : * assigning them while another thread is executing the flows feels like
933 : * tempting fate.
934 : */
935 : if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
936 : {
937 : /* Should not happen... but don't continue if it does. */
938 : Assert(false);
939 :
940 : appendPQExpBuffer(&conn->errorMessage,
941 : "use_builtin_flow: failed to lock mutex (%d)\n",
942 : lockerr);
943 :
944 : request->error = ""; /* satisfy report_flow_error() */
945 : return -1;
946 : }
947 :
948 : if (!initialized)
949 : {
950 : init(
951 : #ifdef ENABLE_NLS
952 : libpq_gettext
953 : #else
954 : NULL
955 : #endif
956 : );
957 :
958 : initialized = true;
959 : }
960 :
961 : pthread_mutex_unlock(&init_mutex);
962 :
963 : return (start_flow(conn, request) == 0) ? 1 : -1;
964 : }
965 :
966 : #else
967 :
968 : /*
969 : * For static builds, we can just call pg_start_oauthbearer() directly. It's
970 : * provided by libpq-oauth.a.
971 : */
972 :
973 : extern int pg_start_oauthbearer(PGconn *conn, PGoauthBearerRequestV2 *request);
974 :
975 : static int
976 : use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
977 : {
978 : return (pg_start_oauthbearer(conn, request) == 0) ? 1 : -1;
979 : }
980 :
981 : #endif /* USE_LIBCURL */
982 :
983 :
984 : /*
985 : * Chooses an OAuth client flow for the connection, which will retrieve a Bearer
986 : * token for presentation to the server.
987 : *
988 : * If the application has registered a custom flow handler using
989 : * PQAUTHDATA_OAUTH_BEARER_TOKEN[_V2], it may either return a token immediately
990 : * (e.g. if it has one cached for immediate use), or set up for a series of
991 : * asynchronous callbacks which will be managed by run_oauth_flow().
992 : *
993 : * If the default handler is used instead, a Device Authorization flow is used
994 : * for the connection if support has been compiled in. (See oauth-curl.c for
995 : * implementation details.)
996 : *
997 : * If neither a custom handler nor the builtin flow is available, the connection
998 : * fails here.
999 : */
1000 : static bool
1001 0 : setup_token_request(PGconn *conn, fe_oauth_state *state)
1002 : {
1003 : int res;
1004 0 : PGoauthBearerRequestV2 request = {
1005 : .v1 = {
1006 0 : .openid_configuration = conn->oauth_discovery_uri,
1007 0 : .scope = conn->oauth_scope,
1008 : },
1009 0 : .issuer = conn->oauth_issuer_id,
1010 : };
1011 :
1012 : Assert(request.v1.openid_configuration);
1013 : Assert(request.issuer);
1014 :
1015 : /*
1016 : * The client may have overridden the OAuth flow. Try the v2 hook first,
1017 : * then fall back to the v1 implementation. If neither is available, try
1018 : * the builtin flow.
1019 : */
1020 0 : res = PQauthDataHook(PQAUTHDATA_OAUTH_BEARER_TOKEN_V2, conn, &request);
1021 0 : if (res == 0)
1022 0 : res = PQauthDataHook(PQAUTHDATA_OAUTH_BEARER_TOKEN, conn, &request);
1023 0 : if (res == 0)
1024 : {
1025 0 : state->builtin = true;
1026 0 : res = use_builtin_flow(conn, state, &request);
1027 : }
1028 :
1029 0 : if (res > 0)
1030 : {
1031 : PGoauthBearerRequestV2 *request_copy;
1032 :
1033 0 : if (request.v1.token)
1034 : {
1035 : /*
1036 : * We already have a token, so copy it into the conn. (We can't
1037 : * hold onto the original string, since it may not be safe for us
1038 : * to free() it.)
1039 : */
1040 0 : conn->oauth_token = strdup(request.v1.token);
1041 0 : if (!conn->oauth_token)
1042 : {
1043 0 : libpq_append_conn_error(conn, "out of memory");
1044 0 : goto fail;
1045 : }
1046 :
1047 : /* short-circuit */
1048 0 : if (request.v1.cleanup)
1049 0 : request.v1.cleanup(conn, (PGoauthBearerRequest *) &request);
1050 0 : return true;
1051 : }
1052 :
1053 0 : request_copy = malloc(sizeof(*request_copy));
1054 0 : if (!request_copy)
1055 : {
1056 0 : libpq_append_conn_error(conn, "out of memory");
1057 0 : goto fail;
1058 : }
1059 :
1060 0 : *request_copy = request;
1061 :
1062 0 : conn->async_auth = run_oauth_flow;
1063 0 : conn->cleanup_async_auth = cleanup_oauth_flow;
1064 0 : state->async_ctx = request_copy;
1065 :
1066 0 : return true;
1067 : }
1068 :
1069 : /*
1070 : * Failure cases: either we tried to set up a flow and failed, or there
1071 : * was no flow to try.
1072 : */
1073 0 : if (res < 0)
1074 0 : report_flow_error(conn, &request);
1075 : else
1076 0 : libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
1077 :
1078 0 : fail:
1079 0 : if (request.v1.cleanup)
1080 0 : request.v1.cleanup(conn, (PGoauthBearerRequest *) &request);
1081 0 : return false;
1082 : }
1083 :
1084 : /*
1085 : * Fill in our issuer identifier (and discovery URI, if possible) using the
1086 : * connection parameters. If conn->oauth_discovery_uri can't be populated in
1087 : * this function, it will be requested from the server.
1088 : */
1089 : static bool
1090 0 : setup_oauth_parameters(PGconn *conn)
1091 : {
1092 : /*
1093 : * This is the only function that sets conn->oauth_issuer_id. If a
1094 : * previous connection attempt has already computed it, don't overwrite it
1095 : * or the discovery URI. (There's no reason for them to change once
1096 : * they're set, and handle_oauth_sasl_error() will fail the connection if
1097 : * the server attempts to switch them on us later.)
1098 : */
1099 0 : if (conn->oauth_issuer_id)
1100 0 : return true;
1101 :
1102 : /*---
1103 : * To talk to a server, we require the user to provide issuer and client
1104 : * identifiers.
1105 : *
1106 : * While it's possible for an OAuth client to support multiple issuers, it
1107 : * requires additional effort to make sure the flows in use are safe -- to
1108 : * quote RFC 9207,
1109 : *
1110 : * OAuth clients that interact with only one authorization server are
1111 : * not vulnerable to mix-up attacks. However, when such clients decide
1112 : * to add support for a second authorization server in the future, they
1113 : * become vulnerable and need to apply countermeasures to mix-up
1114 : * attacks.
1115 : *
1116 : * For now, we allow only one.
1117 : */
1118 0 : if (!conn->oauth_issuer || !conn->oauth_client_id)
1119 : {
1120 0 : libpq_append_conn_error(conn,
1121 : "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
1122 0 : return false;
1123 : }
1124 :
1125 : /*
1126 : * oauth_issuer is interpreted differently if it's a well-known discovery
1127 : * URI rather than just an issuer identifier.
1128 : */
1129 0 : if (strstr(conn->oauth_issuer, WK_PREFIX) != NULL)
1130 : {
1131 : /*
1132 : * Convert the URI back to an issuer identifier. (This also performs
1133 : * validation of the URI format.)
1134 : */
1135 0 : conn->oauth_issuer_id = issuer_from_well_known_uri(conn,
1136 0 : conn->oauth_issuer);
1137 0 : if (!conn->oauth_issuer_id)
1138 0 : return false; /* error message already set */
1139 :
1140 0 : conn->oauth_discovery_uri = strdup(conn->oauth_issuer);
1141 0 : if (!conn->oauth_discovery_uri)
1142 : {
1143 0 : libpq_append_conn_error(conn, "out of memory");
1144 0 : return false;
1145 : }
1146 : }
1147 : else
1148 : {
1149 : /*
1150 : * Treat oauth_issuer as an issuer identifier. We'll ask the server
1151 : * for the discovery URI.
1152 : */
1153 0 : conn->oauth_issuer_id = strdup(conn->oauth_issuer);
1154 0 : if (!conn->oauth_issuer_id)
1155 : {
1156 0 : libpq_append_conn_error(conn, "out of memory");
1157 0 : return false;
1158 : }
1159 : }
1160 :
1161 0 : return true;
1162 : }
1163 :
1164 : /*
1165 : * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2).
1166 : *
1167 : * If the necessary OAuth parameters are set up on the connection, this will run
1168 : * the client flow asynchronously and present the resulting token to the server.
1169 : * Otherwise, an empty discovery response will be sent and any parameters sent
1170 : * back by the server will be stored for a second attempt.
1171 : *
1172 : * For a full description of the API, see libpq/sasl.h.
1173 : */
1174 : static SASLStatus
1175 0 : oauth_exchange(void *opaq, bool final,
1176 : char *input, int inputlen,
1177 : char **output, int *outputlen)
1178 : {
1179 0 : fe_oauth_state *state = opaq;
1180 0 : PGconn *conn = state->conn;
1181 0 : bool discover = false;
1182 :
1183 0 : *output = NULL;
1184 0 : *outputlen = 0;
1185 :
1186 0 : switch (state->step)
1187 : {
1188 0 : case FE_OAUTH_INIT:
1189 : /* We begin in the initial response phase. */
1190 : Assert(inputlen == -1);
1191 :
1192 0 : if (!setup_oauth_parameters(conn))
1193 0 : return SASL_FAILED;
1194 :
1195 0 : if (conn->oauth_token)
1196 : {
1197 : /*
1198 : * A previous connection already fetched the token; we'll use
1199 : * it below.
1200 : */
1201 : }
1202 0 : else if (conn->oauth_discovery_uri)
1203 : {
1204 : /*
1205 : * We don't have a token, but we have a discovery URI already
1206 : * stored. Decide whether we're using a user-provided OAuth
1207 : * flow or the one we have built in.
1208 : */
1209 0 : if (!setup_token_request(conn, state))
1210 0 : return SASL_FAILED;
1211 :
1212 0 : if (conn->oauth_token)
1213 : {
1214 : /*
1215 : * A really smart user implementation may have already
1216 : * given us the token (e.g. if there was an unexpired copy
1217 : * already cached), and we can use it immediately.
1218 : */
1219 : }
1220 : else
1221 : {
1222 : /*
1223 : * Otherwise, we'll have to hand the connection over to
1224 : * our OAuth implementation.
1225 : *
1226 : * This could take a while, since it generally involves a
1227 : * user in the loop. To avoid consuming the server's
1228 : * authentication timeout, we'll continue this handshake
1229 : * to the end, so that the server can close its side of
1230 : * the connection. We'll open a second connection later
1231 : * once we've retrieved a token.
1232 : */
1233 0 : discover = true;
1234 : }
1235 : }
1236 : else
1237 : {
1238 : /*
1239 : * If we don't have a token, and we don't have a discovery URI
1240 : * to be able to request a token, we ask the server for one
1241 : * explicitly.
1242 : */
1243 0 : discover = true;
1244 : }
1245 :
1246 : /*
1247 : * Generate an initial response. This either contains a token, if
1248 : * we have one, or an empty discovery response which is doomed to
1249 : * fail.
1250 : */
1251 0 : *output = client_initial_response(conn, discover);
1252 0 : if (!*output)
1253 0 : return SASL_FAILED;
1254 :
1255 0 : *outputlen = strlen(*output);
1256 0 : state->step = FE_OAUTH_BEARER_SENT;
1257 :
1258 0 : if (conn->oauth_token)
1259 : {
1260 : /*
1261 : * For the purposes of require_auth, our side of
1262 : * authentication is done at this point; the server will
1263 : * either accept the connection or send an error. Unlike
1264 : * SCRAM, there is no additional server data to check upon
1265 : * success.
1266 : */
1267 0 : conn->client_finished_auth = true;
1268 : }
1269 :
1270 0 : return SASL_CONTINUE;
1271 :
1272 0 : case FE_OAUTH_BEARER_SENT:
1273 0 : if (final)
1274 : {
1275 : /*
1276 : * OAUTHBEARER does not make use of additional data with a
1277 : * successful SASL exchange, so we shouldn't get an
1278 : * AuthenticationSASLFinal message.
1279 : */
1280 0 : libpq_append_conn_error(conn,
1281 : "server sent unexpected additional OAuth data");
1282 0 : return SASL_FAILED;
1283 : }
1284 :
1285 : /*
1286 : * An error message was sent by the server. Respond with the
1287 : * required dummy message (RFC 7628, sec. 3.2.3).
1288 : */
1289 0 : *output = strdup(kvsep);
1290 0 : if (unlikely(!*output))
1291 : {
1292 0 : libpq_append_conn_error(conn, "out of memory");
1293 0 : return SASL_FAILED;
1294 : }
1295 0 : *outputlen = strlen(*output); /* == 1 */
1296 :
1297 : /* Grab the settings from discovery. */
1298 0 : if (!handle_oauth_sasl_error(conn, input, inputlen))
1299 0 : return SASL_FAILED;
1300 :
1301 0 : if (conn->oauth_token)
1302 : {
1303 : /*
1304 : * The server rejected our token. Continue onwards towards the
1305 : * expected FATAL message, but mark our state to catch any
1306 : * unexpected "success" from the server.
1307 : */
1308 0 : state->step = FE_OAUTH_SERVER_ERROR;
1309 0 : return SASL_CONTINUE;
1310 : }
1311 :
1312 0 : if (!conn->async_auth)
1313 : {
1314 : /*
1315 : * No OAuth flow is set up yet. Did we get enough information
1316 : * from the server to create one?
1317 : */
1318 0 : if (!conn->oauth_discovery_uri)
1319 : {
1320 0 : libpq_append_conn_error(conn,
1321 : "server requires OAuth authentication, but no discovery metadata was provided");
1322 0 : return SASL_FAILED;
1323 : }
1324 :
1325 : /* Yes. Set up the flow now. */
1326 0 : if (!setup_token_request(conn, state))
1327 0 : return SASL_FAILED;
1328 :
1329 0 : if (conn->oauth_token)
1330 : {
1331 : /*
1332 : * A token was available in a custom flow's cache. Skip
1333 : * the asynchronous processing.
1334 : */
1335 0 : goto reconnect;
1336 : }
1337 : }
1338 :
1339 : /*
1340 : * Time to retrieve a token. This involves a number of HTTP
1341 : * connections and timed waits, so we escape the synchronous auth
1342 : * processing and tell PQconnectPoll to transfer control to our
1343 : * async implementation.
1344 : */
1345 : Assert(conn->async_auth); /* should have been set already */
1346 0 : state->step = FE_OAUTH_REQUESTING_TOKEN;
1347 0 : return SASL_ASYNC;
1348 :
1349 0 : case FE_OAUTH_REQUESTING_TOKEN:
1350 :
1351 : /*
1352 : * We've returned successfully from token retrieval. Double-check
1353 : * that we have what we need for the next connection.
1354 : */
1355 0 : if (!conn->oauth_token)
1356 : {
1357 : Assert(false); /* should have failed before this point! */
1358 0 : libpq_append_conn_error(conn,
1359 : "internal error: OAuth flow did not set a token");
1360 0 : return SASL_FAILED;
1361 : }
1362 :
1363 0 : goto reconnect;
1364 :
1365 0 : case FE_OAUTH_SERVER_ERROR:
1366 :
1367 : /*
1368 : * After an error, the server should send an error response to
1369 : * fail the SASL handshake, which is handled in higher layers.
1370 : *
1371 : * If we get here, the server either sent *another* challenge
1372 : * which isn't defined in the RFC, or completed the handshake
1373 : * successfully after telling us it was going to fail. Neither is
1374 : * acceptable.
1375 : */
1376 0 : libpq_append_conn_error(conn,
1377 : "server sent additional OAuth data after error");
1378 0 : return SASL_FAILED;
1379 :
1380 0 : default:
1381 0 : libpq_append_conn_error(conn, "invalid OAuth exchange state");
1382 0 : break;
1383 : }
1384 :
1385 : Assert(false); /* should never get here */
1386 0 : return SASL_FAILED;
1387 :
1388 0 : reconnect:
1389 :
1390 : /*
1391 : * Despite being a failure from the point of view of SASL, we have enough
1392 : * information to restart with a new connection.
1393 : */
1394 0 : libpq_append_conn_error(conn, "retrying connection with new bearer token");
1395 0 : conn->oauth_want_retry = true;
1396 0 : return SASL_FAILED;
1397 : }
1398 :
1399 : static bool
1400 0 : oauth_channel_bound(void *opaq)
1401 : {
1402 : /* This mechanism does not support channel binding. */
1403 0 : return false;
1404 : }
1405 :
1406 : /*
1407 : * Fully clears out any stored OAuth token. This is done proactively upon
1408 : * successful connection as well as during pqClosePGconn().
1409 : */
1410 : void
1411 29715 : pqClearOAuthToken(PGconn *conn)
1412 : {
1413 29715 : if (!conn->oauth_token)
1414 29715 : return;
1415 :
1416 0 : explicit_bzero(conn->oauth_token, strlen(conn->oauth_token));
1417 0 : free(conn->oauth_token);
1418 0 : conn->oauth_token = NULL;
1419 : }
1420 :
1421 : /*
1422 : * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
1423 : */
1424 : bool
1425 0 : oauth_unsafe_debugging_enabled(void)
1426 : {
1427 0 : const char *env = getenv("PGOAUTHDEBUG");
1428 :
1429 0 : return (env && strcmp(env, "UNSAFE") == 0);
1430 : }
|