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