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