PostgreSQL Source Code git master
Loading...
Searching...
No Matches
fe-auth-oauth.c File Reference
#include "postgres_fe.h"
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
#include "common/oauth-common.h"
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "mb/pg_wchar.h"
#include "oauth-debug.h"
#include "pg_config_paths.h"
#include "utils/memdebug.h"
Include dependency graph for fe-auth-oauth.c:

Go to the source code of this file.

Data Structures

struct  json_ctx
 

Macros

#define kvsep   "\x01"
 
#define ERROR_STATUS_FIELD   "status"
 
#define ERROR_SCOPE_FIELD   "scope"
 
#define ERROR_OPENID_CONFIGURATION_FIELD   "openid-configuration"
 
#define MAX_SASL_NESTING_LEVEL   8
 
#define oauth_json_has_error(ctx)    (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)
 
#define oauth_json_set_error(ctx, fmt, ...)
 
#define oauth_json_set_error_internal(ctx, ...)
 
#define HTTPS_SCHEME   "https://"
 
#define HTTP_SCHEME   "http://"
 
#define WK_PREFIX   "/.well-known/"
 
#define OPENID_WK_SUFFIX   "openid-configuration"
 
#define OAUTH_WK_SUFFIX   "oauth-authorization-server"
 
#define MASK_BITS   ((uintptr_t) 0x55aa55aa55aa55aa)
 
#define POISON_MASK(ptr)   ((void *) (((uintptr_t) ptr) ^ MASK_BITS))
 

Functions

static PostgresPollingStatusType do_async (fe_oauth_state *state, PGoauthBearerRequestV2 *request)
 
static void do_cleanup (fe_oauth_state *state, PGoauthBearerRequestV2 *request)
 
static void poison_req_v2 (PGoauthBearerRequestV2 *request, bool poison)
 
static voidoauth_init (PGconn *conn, const char *password, const char *sasl_mechanism)
 
static SASLStatus oauth_exchange (void *opaq, bool final, char *input, int inputlen, char **output, int *outputlen)
 
static bool oauth_channel_bound (void *opaq)
 
static void oauth_free (void *opaq)
 
static charclient_initial_response (PGconn *conn, bool discover)
 
static JsonParseErrorType oauth_json_object_start (void *state)
 
static JsonParseErrorType oauth_json_object_end (void *state)
 
static JsonParseErrorType oauth_json_object_field_start (void *state, char *name, bool isnull)
 
static JsonParseErrorType oauth_json_array_start (void *state)
 
static JsonParseErrorType oauth_json_array_end (void *state)
 
static JsonParseErrorType oauth_json_scalar (void *state, char *token, JsonTokenType type)
 
static charissuer_from_well_known_uri (PGconn *conn, const char *wkuri)
 
static bool handle_oauth_sasl_error (PGconn *conn, const char *msg, int msglen)
 
static void report_flow_error (PGconn *conn, const PGoauthBearerRequestV2 *request)
 
static PostgresPollingStatusType run_oauth_flow (PGconn *conn)
 
static void cleanup_oauth_flow (PGconn *conn)
 
static int use_builtin_flow (PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
 
static bool setup_token_request (PGconn *conn, fe_oauth_state *state)
 
static bool setup_oauth_parameters (PGconn *conn)
 
void pqClearOAuthToken (PGconn *conn)
 

Variables

const pg_fe_sasl_mech pg_oauth_mech
 

Macro Definition Documentation

◆ ERROR_OPENID_CONFIGURATION_FIELD

#define ERROR_OPENID_CONFIGURATION_FIELD   "openid-configuration"

Definition at line 165 of file fe-auth-oauth.c.

◆ ERROR_SCOPE_FIELD

#define ERROR_SCOPE_FIELD   "scope"

Definition at line 164 of file fe-auth-oauth.c.

◆ ERROR_STATUS_FIELD

#define ERROR_STATUS_FIELD   "status"

Definition at line 163 of file fe-auth-oauth.c.

◆ HTTP_SCHEME

#define HTTP_SCHEME   "http://"

Definition at line 361 of file fe-auth-oauth.c.

◆ HTTPS_SCHEME

#define HTTPS_SCHEME   "https://"

Definition at line 360 of file fe-auth-oauth.c.

◆ kvsep

#define kvsep   "\x01"

Definition at line 101 of file fe-auth-oauth.c.

◆ MASK_BITS

#define MASK_BITS   ((uintptr_t) 0x55aa55aa55aa55aa)

Definition at line 1455 of file fe-auth-oauth.c.

◆ MAX_SASL_NESTING_LEVEL

#define MAX_SASL_NESTING_LEVEL   8

Definition at line 173 of file fe-auth-oauth.c.

◆ oauth_json_has_error

#define oauth_json_has_error (   ctx)     (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)

Definition at line 190 of file fe-auth-oauth.c.

◆ oauth_json_set_error

#define oauth_json_set_error (   ctx,
  fmt,
  ... 
)
Value:
do { \
(ctx)->errmsg = (ctx)->errbuf.data; \
} while (0)
#define libpq_gettext(x)
Definition oauth-utils.h:44
static char * errmsg
static int fb(int x)

Definition at line 193 of file fe-auth-oauth.c.

194 { \
195 appendPQExpBuffer(&(ctx)->errbuf, libpq_gettext(fmt), ##__VA_ARGS__); \
196 (ctx)->errmsg = (ctx)->errbuf.data; \
197 } while (0)

◆ oauth_json_set_error_internal

#define oauth_json_set_error_internal (   ctx,
  ... 
)
Value:
do { \
appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
(ctx)->errmsg = (ctx)->errbuf.data; \
} while (0)

Definition at line 200 of file fe-auth-oauth.c.

201 { \
202 appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
203 (ctx)->errmsg = (ctx)->errbuf.data; \
204 } while (0)

◆ OAUTH_WK_SUFFIX

#define OAUTH_WK_SUFFIX   "oauth-authorization-server"

Definition at line 366 of file fe-auth-oauth.c.

◆ OPENID_WK_SUFFIX

#define OPENID_WK_SUFFIX   "openid-configuration"

Definition at line 365 of file fe-auth-oauth.c.

◆ POISON_MASK

#define POISON_MASK (   ptr)    ((void *) (((uintptr_t) ptr) ^ MASK_BITS))

Definition at line 1456 of file fe-auth-oauth.c.

◆ WK_PREFIX

#define WK_PREFIX   "/.well-known/"

Definition at line 364 of file fe-auth-oauth.c.

Function Documentation

◆ cleanup_oauth_flow()

static void cleanup_oauth_flow ( PGconn conn)
static

Definition at line 805 of file fe-auth-oauth.c.

806{
809
811
814
815 free(request);
816 state->async_ctx = NULL;
817}
#define Assert(condition)
Definition c.h:943
static void do_cleanup(fe_oauth_state *state, PGoauthBearerRequestV2 *request)
#define PGINVALID_SOCKET
Definition port.h:31
#define free(a)
PGconn * conn
Definition streamutil.c:52
pgsocket altsock
Definition libpq-int.h:531
void * sasl_state
Definition libpq-int.h:613

References pg_conn::altsock, Assert, conn, do_cleanup(), fb(), free, PGINVALID_SOCKET, and pg_conn::sasl_state.

Referenced by setup_token_request().

◆ client_initial_response()

static char * client_initial_response ( PGconn conn,
bool  discover 
)
static

Definition at line 113 of file fe-auth-oauth.c.

114{
115 static const char *const resp_format = "n,," kvsep "auth=%s%s" kvsep kvsep;
116
118 const char *authn_scheme;
119 char *response = NULL;
120 const char *token = conn->oauth_token;
121
122 if (discover)
123 {
124 /* Parameter discovery uses a completely empty auth value. */
125 authn_scheme = token = "";
126 }
127 else
128 {
129 /*
130 * Use a Bearer authentication scheme (RFC 6750, Sec. 2.1). A trailing
131 * space is used as a separator.
132 */
133 authn_scheme = "Bearer ";
134
135 /* conn->token must have been set in this case. */
136 if (!token)
137 {
138 Assert(false);
140 "internal error: no OAuth token was set for the connection");
141 return NULL;
142 }
143 }
144
147
149 response = strdup(buf.data);
151
152 if (!response)
153 libpq_append_conn_error(conn, "out of memory");
154
155 return response;
156}
#define kvsep
void libpq_append_conn_error(PGconn *conn, const char *fmt,...)
Definition fe-misc.c:1404
static char buf[DEFAULT_XLOG_SEG_SIZE]
void initPQExpBuffer(PQExpBuffer str)
Definition pqexpbuffer.c:90
void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
void termPQExpBuffer(PQExpBuffer str)
#define PQExpBufferDataBroken(buf)
Definition pqexpbuffer.h:67
char * oauth_token
Definition libpq-int.h:446

References appendPQExpBuffer(), Assert, buf, conn, fb(), initPQExpBuffer(), kvsep, libpq_append_conn_error(), pg_conn::oauth_token, PQExpBufferDataBroken, and termPQExpBuffer().

Referenced by oauth_exchange().

◆ do_async()

static PostgresPollingStatusType do_async ( fe_oauth_state state,
PGoauthBearerRequestV2 request 
)
static

Definition at line 1518 of file fe-auth-oauth.c.

1519{
1521 PGconn *conn = state->conn;
1522
1523 Assert(request->v1.async);
1524
1525 if (state->v1)
1526 poison_req_v2(request, true);
1527
1528 ret = request->v1.async(conn,
1530 &conn->altsock);
1531
1532 if (state->v1)
1533 poison_req_v2(request, false);
1534
1535 return ret;
1536}
static void poison_req_v2(PGoauthBearerRequestV2 *request, bool poison)
PostgresPollingStatusType
Definition libpq-fe.h:120

References pg_conn::altsock, Assert, conn, fb(), and poison_req_v2().

Referenced by run_oauth_flow().

◆ do_cleanup()

static void do_cleanup ( fe_oauth_state state,
PGoauthBearerRequestV2 request 
)
static

Definition at line 1543 of file fe-auth-oauth.c.

1544{
1545 if (!request->v1.cleanup)
1546 return;
1547
1548 if (state->v1)
1549 poison_req_v2(request, true);
1550
1551 request->v1.cleanup(state->conn, (PGoauthBearerRequest *) request);
1552
1553 if (state->v1)
1554 poison_req_v2(request, false);
1555}

References fb(), and poison_req_v2().

Referenced by cleanup_oauth_flow(), and setup_token_request().

◆ handle_oauth_sasl_error()

static bool handle_oauth_sasl_error ( PGconn conn,
const char msg,
int  msglen 
)
static

Definition at line 520 of file fe-auth-oauth.c.

521{
522 JsonLexContext *lex;
523 JsonSemAction sem = {0};
525 struct json_ctx ctx = {0};
526 char *errmsg = NULL;
527 bool success = false;
528
529 Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
530
531 /* Sanity check. */
532 if (strlen(msg) != msglen)
533 {
535 "server's error message contained an embedded NULL, and was discarded");
536 return false;
537 }
538
539 /*
540 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
541 * that up front.
542 */
544 {
546 "server's error response is not valid UTF-8");
547 return false;
548 }
549
551 setJsonLexContextOwnsTokens(lex, true); /* must not leak on error */
552
554 sem.semstate = &ctx;
555
562
563 err = pg_parse_json(lex, &sem);
564
566 {
568 errmsg = libpq_gettext("out of memory");
569 else if (ctx.errmsg)
570 errmsg = ctx.errmsg;
571 else
572 {
573 /*
574 * Developer error: one of the action callbacks didn't call
575 * oauth_json_set_error() before erroring out.
576 */
578 errmsg = "<unexpected empty error>";
579 }
580 }
581 else if (err != JSON_SUCCESS)
582 errmsg = json_errdetail(err, lex);
583
584 if (errmsg)
586 "failed to parse server's error response: %s",
587 errmsg);
588
589 /* Don't need the error buffer or the JSON lexer anymore. */
592
593 if (errmsg)
594 goto cleanup;
595
596 if (ctx.discovery_uri)
597 {
598 char *discovery_issuer;
599
600 /*
601 * The URI MUST correspond to our existing issuer, to avoid mix-ups.
602 *
603 * Issuer comparison is done byte-wise, rather than performing any URL
604 * normalization; this follows the suggestions for issuer comparison
605 * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
606 * vastly simplifies things. Since this is the key protection against
607 * a rogue server sending the client to an untrustworthy location,
608 * simpler is better.
609 */
611 if (!discovery_issuer)
612 goto cleanup; /* error message already set */
613
615 {
617 "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
620
622 goto cleanup;
623 }
624
626
628 {
630 ctx.discovery_uri = NULL;
631 }
632 else
633 {
634 /* This must match the URI we'd previously determined. */
636 {
638 "server's discovery document has moved to %s (previous location was %s)",
639 ctx.discovery_uri,
641 goto cleanup;
642 }
643 }
644 }
645
646 if (ctx.scope)
647 {
648 /* Servers may not override a previously set oauth_scope. */
649 if (!conn->oauth_scope)
650 {
651 conn->oauth_scope = ctx.scope;
652 ctx.scope = NULL;
653 }
654 }
655
656 if (!ctx.status)
657 {
659 "server sent error response without a status");
660 goto cleanup;
661 }
662
663 if (strcmp(ctx.status, "invalid_token") != 0)
664 {
665 /*
666 * invalid_token is the only error code we'll automatically retry for;
667 * otherwise, just bail out now.
668 */
670 "server rejected OAuth bearer token: %s",
671 ctx.status);
672 goto cleanup;
673 }
674
675 success = true;
676
677cleanup:
678 free(ctx.status);
679 free(ctx.scope);
680 free(ctx.discovery_uri);
681
682 return success;
683}
static void cleanup(void)
Definition bootstrap.c:886
void err(int eval, const char *fmt,...)
Definition err.c:43
static JsonParseErrorType oauth_json_array_end(void *state)
static char * issuer_from_well_known_uri(PGconn *conn, const char *wkuri)
static JsonParseErrorType oauth_json_object_field_start(void *state, char *name, bool isnull)
static JsonParseErrorType oauth_json_scalar(void *state, char *token, JsonTokenType type)
#define oauth_json_has_error(ctx)
static JsonParseErrorType oauth_json_array_start(void *state)
static JsonParseErrorType oauth_json_object_end(void *state)
static JsonParseErrorType oauth_json_object_start(void *state)
static bool success
Definition initdb.c:188
JsonParseErrorType pg_parse_json(JsonLexContext *lex, const JsonSemAction *sem)
Definition jsonapi.c:744
JsonLexContext * makeJsonLexContextCstringLen(JsonLexContext *lex, const char *json, size_t len, int encoding, bool need_escapes)
Definition jsonapi.c:392
void setJsonLexContextOwnsTokens(JsonLexContext *lex, bool owned_by_context)
Definition jsonapi.c:542
char * json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
Definition jsonapi.c:2404
void freeJsonLexContext(JsonLexContext *lex)
Definition jsonapi.c:687
JsonParseErrorType
Definition jsonapi.h:35
@ JSON_SEM_ACTION_FAILED
Definition jsonapi.h:59
@ JSON_SUCCESS
Definition jsonapi.h:36
#define PG_UTF8
Definition mbprint.c:43
json_struct_action array_end
Definition jsonapi.h:157
json_struct_action object_start
Definition jsonapi.h:154
json_ofield_action object_field_start
Definition jsonapi.h:158
json_scalar_action scalar
Definition jsonapi.h:162
void * semstate
Definition jsonapi.h:153
json_struct_action array_start
Definition jsonapi.h:156
json_struct_action object_end
Definition jsonapi.h:155
char * discovery_uri
char * status
char * scope
PQExpBufferData errbuf
char * errmsg
char * oauth_discovery_uri
Definition libpq-int.h:441
char * oauth_scope
Definition libpq-int.h:445
char * oauth_issuer_id
Definition libpq-int.h:440
static JsonSemAction sem
int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len)
Definition wchar.c:2001

References JsonSemAction::array_end, JsonSemAction::array_start, Assert, cleanup(), conn, json_ctx::discovery_uri, err(), json_ctx::errbuf, json_ctx::errmsg, errmsg, fb(), free, freeJsonLexContext(), initPQExpBuffer(), issuer_from_well_known_uri(), json_errdetail(), JSON_SEM_ACTION_FAILED, JSON_SUCCESS, libpq_append_conn_error(), libpq_gettext, makeJsonLexContextCstringLen(), pg_conn::oauth_discovery_uri, pg_conn::oauth_issuer_id, oauth_json_array_end(), oauth_json_array_start(), oauth_json_has_error, oauth_json_object_end(), oauth_json_object_field_start(), oauth_json_object_start(), oauth_json_scalar(), pg_conn::oauth_scope, JsonSemAction::object_end, JsonSemAction::object_field_start, JsonSemAction::object_start, pg_encoding_verifymbstr(), pg_parse_json(), PG_UTF8, PQExpBufferDataBroken, JsonSemAction::scalar, json_ctx::scope, sem, JsonSemAction::semstate, setJsonLexContextOwnsTokens(), json_ctx::status, success, and termPQExpBuffer().

Referenced by oauth_exchange().

◆ issuer_from_well_known_uri()

static char * issuer_from_well_known_uri ( PGconn conn,
const char wkuri 
)
static

Definition at line 373 of file fe-auth-oauth.c.

374{
375 const char *authority_start = NULL;
376 const char *wk_start;
377 const char *wk_end;
378 char *issuer;
381 size_t end_len;
382
383 /*
384 * https:// is required for issuer identifiers (RFC 8414, Sec. 2; OIDC
385 * Discovery 1.0, Sec. 3). This is a case-insensitive comparison at this
386 * level (but issuer identifier comparison at the level above this is
387 * case-sensitive, so in practice it's probably moot).
388 */
391
392 if (!authority_start
395 {
396 /* Allow http:// for testing only. */
398 }
399
400 if (!authority_start)
401 {
403 "OAuth discovery URI \"%s\" must use HTTPS",
404 wkuri);
405 return NULL;
406 }
407
408 /*
409 * Well-known URIs in general may support queries and fragments, but the
410 * two types we support here do not. (They must be constructed from the
411 * components of issuer identifiers, which themselves may not contain any
412 * queries or fragments.)
413 *
414 * It's important to check this first, to avoid getting tricked later by a
415 * prefix buried inside a query or fragment.
416 */
417 if (strpbrk(authority_start, "?#") != NULL)
418 {
420 "OAuth discovery URI \"%s\" must not contain query or fragment components",
421 wkuri);
422 return NULL;
423 }
424
425 /*
426 * Find the start of the .well-known prefix. IETF rules (RFC 8615) state
427 * this must be at the beginning of the path component, but OIDC defined
428 * it at the end instead (OIDC Discovery 1.0, Sec. 4), so we have to
429 * search for it anywhere.
430 */
432 if (!wk_start)
433 {
435 "OAuth discovery URI \"%s\" is not a .well-known URI",
436 wkuri);
437 return NULL;
438 }
439
440 /*
441 * Now find the suffix type. We only support the two defined in OIDC
442 * Discovery 1.0 and RFC 8414.
443 */
445
450 else
451 wk_end = NULL;
452
453 /*
454 * Even if there's a match, we still need to check to make sure the suffix
455 * takes up the entire path segment, to weed out constructions like
456 * "/.well-known/openid-configuration-bad".
457 */
458 if (!wk_end || (*wk_end != '/' && *wk_end != '\0'))
459 {
461 "OAuth discovery URI \"%s\" uses an unsupported .well-known suffix",
462 wkuri);
463 return NULL;
464 }
465
466 /*
467 * Finally, make sure the .well-known components are provided either as a
468 * prefix (IETF style) or as a postfix (OIDC style). In other words,
469 * "https://localhost/a/.well-known/openid-configuration/b" is not allowed
470 * to claim association with "https://localhost/a/b".
471 */
472 if (*wk_end != '\0')
473 {
474 /*
475 * It's not at the end, so it's required to be at the beginning at the
476 * path. Find the starting slash.
477 */
478 const char *path_start;
479
481 Assert(path_start); /* otherwise we wouldn't have found WK_PREFIX */
482
483 if (wk_start != path_start)
484 {
486 "OAuth discovery URI \"%s\" uses an invalid format",
487 wkuri);
488 return NULL;
489 }
490 }
491
492 /* Checks passed! Now build the issuer. */
493 issuer = strdup(wkuri);
494 if (!issuer)
495 {
496 libpq_append_conn_error(conn, "out of memory");
497 return NULL;
498 }
499
500 /*
501 * The .well-known components are from [wk_start, wk_end). Remove those to
502 * form the issuer ID, by shifting the path suffix (which may be empty)
503 * leftwards.
504 */
507 end_len = strlen(wk_end) + 1; /* move the NULL terminator too */
508
509 memmove(issuer + start_offset, issuer + end_offset, end_len);
510
511 return issuer;
512}
#define HTTP_SCHEME
#define HTTPS_SCHEME
#define WK_PREFIX
#define OPENID_WK_SUFFIX
#define OAUTH_WK_SUFFIX
#define OAUTHDEBUG_UNSAFE_HTTP
Definition oauth-debug.h:38
static uint32 oauth_parse_debug_flags(void)
Definition oauth-debug.h:79
int pg_strncasecmp(const char *s1, const char *s2, size_t n)

References Assert, conn, fb(), HTTP_SCHEME, HTTPS_SCHEME, libpq_append_conn_error(), oauth_parse_debug_flags(), OAUTH_WK_SUFFIX, OAUTHDEBUG_UNSAFE_HTTP, OPENID_WK_SUFFIX, pg_strncasecmp(), and WK_PREFIX.

Referenced by handle_oauth_sasl_error(), and setup_oauth_parameters().

◆ oauth_channel_bound()

static bool oauth_channel_bound ( void opaq)
static

Definition at line 1420 of file fe-auth-oauth.c.

1421{
1422 /* This mechanism does not support channel binding. */
1423 return false;
1424}

◆ oauth_exchange()

static SASLStatus oauth_exchange ( void opaq,
bool  final,
char input,
int  inputlen,
char **  output,
int outputlen 
)
static

Definition at line 1195 of file fe-auth-oauth.c.

1198{
1200 PGconn *conn = state->conn;
1201 bool discover = false;
1202
1203 *output = NULL;
1204 *outputlen = 0;
1205
1206 switch (state->step)
1207 {
1208 case FE_OAUTH_INIT:
1209 /* We begin in the initial response phase. */
1210 Assert(inputlen == -1);
1211
1213 return SASL_FAILED;
1214
1215 if (conn->oauth_token)
1216 {
1217 /*
1218 * A previous connection already fetched the token; we'll use
1219 * it below.
1220 */
1221 }
1222 else if (conn->oauth_discovery_uri)
1223 {
1224 /*
1225 * We don't have a token, but we have a discovery URI already
1226 * stored. Decide whether we're using a user-provided OAuth
1227 * flow or the one we have built in.
1228 */
1230 return SASL_FAILED;
1231
1232 if (conn->oauth_token)
1233 {
1234 /*
1235 * A really smart user implementation may have already
1236 * given us the token (e.g. if there was an unexpired copy
1237 * already cached), and we can use it immediately.
1238 */
1239 }
1240 else
1241 {
1242 /*
1243 * Otherwise, we'll have to hand the connection over to
1244 * our OAuth implementation.
1245 *
1246 * This could take a while, since it generally involves a
1247 * user in the loop. To avoid consuming the server's
1248 * authentication timeout, we'll continue this handshake
1249 * to the end, so that the server can close its side of
1250 * the connection. We'll open a second connection later
1251 * once we've retrieved a token.
1252 */
1253 discover = true;
1254 }
1255 }
1256 else
1257 {
1258 /*
1259 * If we don't have a token, and we don't have a discovery URI
1260 * to be able to request a token, we ask the server for one
1261 * explicitly.
1262 */
1263 discover = true;
1264 }
1265
1266 /*
1267 * Generate an initial response. This either contains a token, if
1268 * we have one, or an empty discovery response which is doomed to
1269 * fail.
1270 */
1272 if (!*output)
1273 return SASL_FAILED;
1274
1275 *outputlen = strlen(*output);
1277
1278 if (conn->oauth_token)
1279 {
1280 /*
1281 * For the purposes of require_auth, our side of
1282 * authentication is done at this point; the server will
1283 * either accept the connection or send an error. Unlike
1284 * SCRAM, there is no additional server data to check upon
1285 * success.
1286 */
1287 conn->client_finished_auth = true;
1288 }
1289
1290 return SASL_CONTINUE;
1291
1293 if (final)
1294 {
1295 /*
1296 * OAUTHBEARER does not make use of additional data with a
1297 * successful SASL exchange, so we shouldn't get an
1298 * AuthenticationSASLFinal message.
1299 */
1301 "server sent unexpected additional OAuth data");
1302 return SASL_FAILED;
1303 }
1304
1305 /*
1306 * An error message was sent by the server. Respond with the
1307 * required dummy message (RFC 7628, sec. 3.2.3).
1308 */
1309 *output = strdup(kvsep);
1310 if (unlikely(!*output))
1311 {
1312 libpq_append_conn_error(conn, "out of memory");
1313 return SASL_FAILED;
1314 }
1315 *outputlen = strlen(*output); /* == 1 */
1316
1317 /* Grab the settings from discovery. */
1319 return SASL_FAILED;
1320
1321 if (conn->oauth_token)
1322 {
1323 /*
1324 * The server rejected our token. Continue onwards towards the
1325 * expected FATAL message, but mark our state to catch any
1326 * unexpected "success" from the server.
1327 */
1329 return SASL_CONTINUE;
1330 }
1331
1332 if (!conn->async_auth)
1333 {
1334 /*
1335 * No OAuth flow is set up yet. Did we get enough information
1336 * from the server to create one?
1337 */
1339 {
1341 "server requires OAuth authentication, but no discovery metadata was provided");
1342 return SASL_FAILED;
1343 }
1344
1345 /* Yes. Set up the flow now. */
1347 return SASL_FAILED;
1348
1349 if (conn->oauth_token)
1350 {
1351 /*
1352 * A token was available in a custom flow's cache. Skip
1353 * the asynchronous processing.
1354 */
1355 goto reconnect;
1356 }
1357 }
1358
1359 /*
1360 * Time to retrieve a token. This involves a number of HTTP
1361 * connections and timed waits, so we escape the synchronous auth
1362 * processing and tell PQconnectPoll to transfer control to our
1363 * async implementation.
1364 */
1365 Assert(conn->async_auth); /* should have been set already */
1367 return SASL_ASYNC;
1368
1370
1371 /*
1372 * We've returned successfully from token retrieval. Double-check
1373 * that we have what we need for the next connection.
1374 */
1375 if (!conn->oauth_token)
1376 {
1377 Assert(false); /* should have failed before this point! */
1379 "internal error: OAuth flow did not set a token");
1380 return SASL_FAILED;
1381 }
1382
1383 goto reconnect;
1384
1386
1387 /*
1388 * After an error, the server should send an error response to
1389 * fail the SASL handshake, which is handled in higher layers.
1390 *
1391 * If we get here, the server either sent *another* challenge
1392 * which isn't defined in the RFC, or completed the handshake
1393 * successfully after telling us it was going to fail. Neither is
1394 * acceptable.
1395 */
1397 "server sent additional OAuth data after error");
1398 return SASL_FAILED;
1399
1400 default:
1401 libpq_append_conn_error(conn, "invalid OAuth exchange state");
1402 break;
1403 }
1404
1405 Assert(false); /* should never get here */
1406 return SASL_FAILED;
1407
1408reconnect:
1409
1410 /*
1411 * Despite being a failure from the point of view of SASL, we have enough
1412 * information to restart with a new connection.
1413 */
1414 libpq_append_conn_error(conn, "retrying connection with new bearer token");
1415 conn->oauth_want_retry = true;
1416 return SASL_FAILED;
1417}
#define unlikely(x)
Definition c.h:438
static bool setup_token_request(PGconn *conn, fe_oauth_state *state)
static bool handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
static bool setup_oauth_parameters(PGconn *conn)
static char * client_initial_response(PGconn *conn, bool discover)
@ FE_OAUTH_REQUESTING_TOKEN
@ FE_OAUTH_SERVER_ERROR
@ FE_OAUTH_INIT
@ FE_OAUTH_BEARER_SENT
@ SASL_ASYNC
@ SASL_CONTINUE
@ SASL_FAILED
FILE * input
FILE * output
bool client_finished_auth
Definition libpq-int.h:522
bool oauth_want_retry
Definition libpq-int.h:448
PostgresPollingStatusType(* async_auth)(PGconn *conn)
Definition libpq-int.h:529

References Assert, pg_conn::async_auth, pg_conn::client_finished_auth, client_initial_response(), conn, fb(), FE_OAUTH_BEARER_SENT, FE_OAUTH_INIT, FE_OAUTH_REQUESTING_TOKEN, FE_OAUTH_SERVER_ERROR, handle_oauth_sasl_error(), input, kvsep, libpq_append_conn_error(), pg_conn::oauth_discovery_uri, pg_conn::oauth_token, pg_conn::oauth_want_retry, output, SASL_ASYNC, SASL_CONTINUE, SASL_FAILED, setup_oauth_parameters(), setup_token_request(), and unlikely.

◆ oauth_free()

static void oauth_free ( void opaq)
static

Definition at line 91 of file fe-auth-oauth.c.

92{
94
95 /* Any async authentication state should have been cleaned up already. */
96 Assert(!state->async_ctx);
97
98 free(state);
99}

References Assert, fb(), and free.

◆ oauth_init()

static void * oauth_init ( PGconn conn,
const char password,
const char sasl_mechanism 
)
static

Definition at line 60 of file fe-auth-oauth.c.

62{
64
65 /*
66 * We only support one SASL mechanism here; anything else is programmer
67 * error.
68 */
69 Assert(sasl_mechanism != NULL);
70 Assert(strcmp(sasl_mechanism, OAUTHBEARER_NAME) == 0);
71
72 state = calloc(1, sizeof(*state));
73 if (!state)
74 return NULL;
75
76 state->step = FE_OAUTH_INIT;
77 state->conn = conn;
78
79 return state;
80}
#define OAUTHBEARER_NAME
#define calloc(a, b)

References Assert, calloc, conn, fb(), FE_OAUTH_INIT, and OAUTHBEARER_NAME.

◆ oauth_json_array_end()

static JsonParseErrorType oauth_json_array_end ( void state)
static

Definition at line 290 of file fe-auth-oauth.c.

291{
292 struct json_ctx *ctx = state;
293
294 --ctx->nested;
295 return JSON_SUCCESS;
296}

References JSON_SUCCESS, and json_ctx::nested.

Referenced by handle_oauth_sasl_error().

◆ oauth_json_array_start()

static JsonParseErrorType oauth_json_array_start ( void state)
static

Definition at line 265 of file fe-auth-oauth.c.

266{
267 struct json_ctx *ctx = state;
268
269 if (!ctx->nested)
270 {
271 oauth_json_set_error(ctx, "top-level element must be an object");
272 }
273 else if (ctx->target_field)
274 {
275 Assert(ctx->nested == 1);
276
278 "field \"%s\" must be a string",
279 ctx->target_field_name);
280 }
281
282 ++ctx->nested;
284 oauth_json_set_error(ctx, "JSON is too deeply nested");
285
287}
#define oauth_json_set_error(ctx, fmt,...)
#define MAX_SASL_NESTING_LEVEL
const char * target_field_name
char ** target_field

References Assert, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, MAX_SASL_NESTING_LEVEL, json_ctx::nested, oauth_json_has_error, oauth_json_set_error, json_ctx::target_field, and json_ctx::target_field_name.

Referenced by handle_oauth_sasl_error().

◆ oauth_json_object_end()

static JsonParseErrorType oauth_json_object_end ( void state)
static

Definition at line 228 of file fe-auth-oauth.c.

229{
230 struct json_ctx *ctx = state;
231
232 --ctx->nested;
233 return JSON_SUCCESS;
234}

References JSON_SUCCESS, and json_ctx::nested.

Referenced by handle_oauth_sasl_error().

◆ oauth_json_object_field_start()

static JsonParseErrorType oauth_json_object_field_start ( void state,
char name,
bool  isnull 
)
static

Definition at line 237 of file fe-auth-oauth.c.

238{
239 struct json_ctx *ctx = state;
240
241 /* Only top-level keys are considered. */
242 if (ctx->nested == 1)
243 {
244 if (strcmp(name, ERROR_STATUS_FIELD) == 0)
245 {
247 ctx->target_field = &ctx->status;
248 }
249 else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
250 {
252 ctx->target_field = &ctx->scope;
253 }
255 {
257 ctx->target_field = &ctx->discovery_uri;
258 }
259 }
260
261 return JSON_SUCCESS;
262}
#define ERROR_SCOPE_FIELD
#define ERROR_OPENID_CONFIGURATION_FIELD
#define ERROR_STATUS_FIELD
const char * name

References json_ctx::discovery_uri, ERROR_OPENID_CONFIGURATION_FIELD, ERROR_SCOPE_FIELD, ERROR_STATUS_FIELD, fb(), JSON_SUCCESS, name, json_ctx::nested, json_ctx::scope, json_ctx::status, json_ctx::target_field, and json_ctx::target_field_name.

Referenced by handle_oauth_sasl_error().

◆ oauth_json_object_start()

static JsonParseErrorType oauth_json_object_start ( void state)
static

Definition at line 207 of file fe-auth-oauth.c.

208{
209 struct json_ctx *ctx = state;
210
211 if (ctx->target_field)
212 {
213 Assert(ctx->nested == 1);
214
216 "field \"%s\" must be a string",
217 ctx->target_field_name);
218 }
219
220 ++ctx->nested;
222 oauth_json_set_error(ctx, "JSON is too deeply nested");
223
225}

References Assert, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, MAX_SASL_NESTING_LEVEL, json_ctx::nested, oauth_json_has_error, oauth_json_set_error, json_ctx::target_field, and json_ctx::target_field_name.

Referenced by handle_oauth_sasl_error().

◆ oauth_json_scalar()

static JsonParseErrorType oauth_json_scalar ( void state,
char token,
JsonTokenType  type 
)
static

Definition at line 299 of file fe-auth-oauth.c.

300{
301 struct json_ctx *ctx = state;
302
303 if (!ctx->nested)
304 {
305 oauth_json_set_error(ctx, "top-level element must be an object");
307 }
308
309 if (ctx->target_field)
310 {
311 if (ctx->nested != 1)
312 {
313 /*
314 * ctx->target_field should not have been set for nested keys.
315 * Assert and don't continue any further for production builds.
316 */
317 Assert(false);
319 "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
320 ctx->nested);
322 }
323
324 /*
325 * We don't allow duplicate field names; error out if the target has
326 * already been set.
327 */
328 if (*ctx->target_field)
329 {
331 "field \"%s\" is duplicated",
332 ctx->target_field_name);
334 }
335
336 /* The only fields we support are strings. */
337 if (type != JSON_TOKEN_STRING)
338 {
340 "field \"%s\" must be a string",
341 ctx->target_field_name);
343 }
344
345 *ctx->target_field = strdup(token);
346 if (!*ctx->target_field)
347 return JSON_OUT_OF_MEMORY;
348
349 ctx->target_field = NULL;
350 ctx->target_field_name = NULL;
351 }
352 else
353 {
354 /* otherwise we just ignore it */
355 }
356
357 return JSON_SUCCESS;
358}
#define oauth_json_set_error_internal(ctx,...)
@ JSON_OUT_OF_MEMORY
Definition jsonapi.h:52
@ JSON_TOKEN_STRING
Definition jsonapi.h:20
const char * type

References Assert, fb(), JSON_OUT_OF_MEMORY, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, JSON_TOKEN_STRING, json_ctx::nested, oauth_json_set_error, oauth_json_set_error_internal, json_ctx::target_field, json_ctx::target_field_name, and type.

Referenced by handle_oauth_sasl_error().

◆ poison_req_v2()

static void poison_req_v2 ( PGoauthBearerRequestV2 request,
bool  poison 
)
static

Definition at line 1467 of file fe-auth-oauth.c.

1468{
1469#ifdef USE_VALGRIND
1470 void *const base = (char *) request + sizeof(request->v1);
1471 const size_t len = sizeof(*request) - sizeof(request->v1);
1472#endif
1473
1474 if (poison)
1475 {
1476 /* Poison request->issuer with a mask to help uninstrumented builds. */
1477 request->issuer = POISON_MASK(request->issuer);
1478
1479 /*
1480 * We'll check to make sure request->error wasn't assigned when
1481 * unpoisoning, so it had better not be assigned now.
1482 */
1483 Assert(!request->error);
1484
1486 }
1487 else
1488 {
1489 /*
1490 * XXX Using DEFINED here is technically too lax; we might catch
1491 * struct padding in the blast radius. But since this API has to
1492 * poison stack addresses, and Valgrind can't track/manage undefined
1493 * stack regions, we can't be any stricter without tracking the
1494 * original state of the memory.
1495 */
1497
1498 /* Undo our mask. */
1499 request->issuer = POISON_MASK(request->issuer);
1500
1501 /*
1502 * For uninstrumented builds, make sure request->error wasn't touched.
1503 */
1504 if (request->error)
1505 {
1507 "abort! out-of-bounds write to PGoauthBearerRequest by PQAUTHDATA_OAUTH_BEARER_TOKEN hook\n");
1508 abort();
1509 }
1510 }
1511}
#define fprintf(file, fmt, msg)
Definition cubescan.l:21
#define POISON_MASK(ptr)
#define VALGRIND_MAKE_MEM_DEFINED(addr, size)
Definition memdebug.h:26
#define VALGRIND_MAKE_MEM_NOACCESS(addr, size)
Definition memdebug.h:27
const void size_t len

References Assert, fb(), fprintf, len, POISON_MASK, VALGRIND_MAKE_MEM_DEFINED, and VALGRIND_MAKE_MEM_NOACCESS.

Referenced by do_async(), do_cleanup(), and setup_token_request().

◆ pqClearOAuthToken()

void pqClearOAuthToken ( PGconn conn)

Definition at line 1431 of file fe-auth-oauth.c.

1432{
1433 if (!conn->oauth_token)
1434 return;
1435
1439}
void explicit_bzero(void *buf, size_t len)

References conn, explicit_bzero(), fb(), free, and pg_conn::oauth_token.

Referenced by pqClosePGconn(), and PQconnectPoll().

◆ report_flow_error()

static void report_flow_error ( PGconn conn,
const PGoauthBearerRequestV2 request 
)
static

Definition at line 690 of file fe-auth-oauth.c.

691{
693 const char *errmsg = request->error;
694
695 /*
696 * User-defined flows are called out explicitly so that the user knows who
697 * to blame. Builtin flows don't need that extra message length; we expect
698 * them to always fill in request->error on failure anyway.
699 */
700 if (state->builtin)
701 {
702 if (!errmsg)
703 {
704 /*
705 * Don't turn a bug here into a crash in production, but don't
706 * bother translating either.
707 */
708 Assert(false);
709 errmsg = "builtin flow failed but did not provide an error message";
710 }
711
713 }
714 else
715 {
717 libpq_gettext("user-defined OAuth flow failed"));
718 if (errmsg)
719 {
722 }
723 }
724
726}
void appendPQExpBufferChar(PQExpBuffer str, char ch)
void appendPQExpBufferStr(PQExpBuffer str, const char *data)
PQExpBufferData errorMessage
Definition libpq-int.h:684

References appendPQExpBufferChar(), appendPQExpBufferStr(), Assert, conn, errmsg, pg_conn::errorMessage, fb(), libpq_gettext, and pg_conn::sasl_state.

Referenced by run_oauth_flow(), and setup_token_request().

◆ run_oauth_flow()

static PostgresPollingStatusType run_oauth_flow ( PGconn conn)
static

Definition at line 737 of file fe-auth-oauth.c.

738{
742
743 if (!request->v1.async)
744 {
745 Assert(!state->builtin); /* be very noisy if our code does this */
747 "user-defined OAuth flow provided neither a token nor an async callback");
749 }
750
751 status = do_async(state, request);
752
753 if (status == PGRES_POLLING_FAILED)
754 {
756 return status;
757 }
758 else if (status == PGRES_POLLING_OK)
759 {
760 /*
761 * We already have a token, so copy it into the conn. (We can't hold
762 * onto the original string, since it may not be safe for us to free()
763 * it.)
764 */
765 if (!request->v1.token)
766 {
767 Assert(!state->builtin);
769 "user-defined OAuth flow did not provide a token");
771 }
772
773 conn->oauth_token = strdup(request->v1.token);
774 if (!conn->oauth_token)
775 {
776 libpq_append_conn_error(conn, "out of memory");
778 }
779
780 return PGRES_POLLING_OK;
781 }
782
783 /* The hook wants the client to poll the altsock. Make sure it set one. */
785 {
786 Assert(!state->builtin);
788 "user-defined OAuth flow did not provide a socket for polling");
790 }
791
792 return status;
793}
static PostgresPollingStatusType do_async(fe_oauth_state *state, PGoauthBearerRequestV2 *request)
static void report_flow_error(PGconn *conn, const PGoauthBearerRequestV2 *request)
@ PGRES_POLLING_OK
Definition libpq-fe.h:124
@ PGRES_POLLING_FAILED
Definition libpq-fe.h:121

References pg_conn::altsock, Assert, conn, do_async(), fb(), libpq_append_conn_error(), pg_conn::oauth_token, PGINVALID_SOCKET, PGRES_POLLING_FAILED, PGRES_POLLING_OK, report_flow_error(), pg_conn::sasl_state, and json_ctx::status.

Referenced by setup_token_request().

◆ setup_oauth_parameters()

static bool setup_oauth_parameters ( PGconn conn)
static

Definition at line 1110 of file fe-auth-oauth.c.

1111{
1112 /*
1113 * This is the only function that sets conn->oauth_issuer_id. If a
1114 * previous connection attempt has already computed it, don't overwrite it
1115 * or the discovery URI. (There's no reason for them to change once
1116 * they're set, and handle_oauth_sasl_error() will fail the connection if
1117 * the server attempts to switch them on us later.)
1118 */
1119 if (conn->oauth_issuer_id)
1120 return true;
1121
1122 /*---
1123 * To talk to a server, we require the user to provide issuer and client
1124 * identifiers.
1125 *
1126 * While it's possible for an OAuth client to support multiple issuers, it
1127 * requires additional effort to make sure the flows in use are safe -- to
1128 * quote RFC 9207,
1129 *
1130 * OAuth clients that interact with only one authorization server are
1131 * not vulnerable to mix-up attacks. However, when such clients decide
1132 * to add support for a second authorization server in the future, they
1133 * become vulnerable and need to apply countermeasures to mix-up
1134 * attacks.
1135 *
1136 * For now, we allow only one.
1137 */
1139 {
1141 "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
1142 return false;
1143 }
1144
1145 /*
1146 * oauth_issuer is interpreted differently if it's a well-known discovery
1147 * URI rather than just an issuer identifier.
1148 */
1150 {
1151 /*
1152 * Convert the URI back to an issuer identifier. (This also performs
1153 * validation of the URI format.)
1154 */
1157 if (!conn->oauth_issuer_id)
1158 return false; /* error message already set */
1159
1162 {
1163 libpq_append_conn_error(conn, "out of memory");
1164 return false;
1165 }
1166 }
1167 else
1168 {
1169 /*
1170 * Treat oauth_issuer as an issuer identifier. We'll ask the server
1171 * for the discovery URI.
1172 */
1174 if (!conn->oauth_issuer_id)
1175 {
1176 libpq_append_conn_error(conn, "out of memory");
1177 return false;
1178 }
1179 }
1180
1181 return true;
1182}
char * oauth_client_id
Definition libpq-int.h:443
char * oauth_issuer
Definition libpq-int.h:439

References conn, fb(), issuer_from_well_known_uri(), libpq_append_conn_error(), pg_conn::oauth_client_id, pg_conn::oauth_discovery_uri, pg_conn::oauth_issuer, pg_conn::oauth_issuer_id, and WK_PREFIX.

Referenced by oauth_exchange().

◆ setup_token_request()

static bool setup_token_request ( PGconn conn,
fe_oauth_state state 
)
static

Definition at line 1016 of file fe-auth-oauth.c.

1017{
1018 int res;
1020 .v1 = {
1022 .scope = conn->oauth_scope,
1023 },
1024 .issuer = conn->oauth_issuer_id,
1025 };
1026
1027 Assert(request.v1.openid_configuration);
1028 Assert(request.issuer);
1029
1030 /*
1031 * The client may have overridden the OAuth flow. Try the v2 hook first,
1032 * then fall back to the v1 implementation. If neither is available, try
1033 * the builtin flow.
1034 */
1036 if (res == 0)
1037 {
1038 poison_req_v2(&request, true);
1039
1041 state->v1 = (res != 0);
1042
1043 poison_req_v2(&request, false);
1044 }
1045 if (res == 0)
1046 {
1047 state->builtin = true;
1049 }
1050
1051 if (res > 0)
1052 {
1054
1055 if (request.v1.token)
1056 {
1057 /*
1058 * We already have a token, so copy it into the conn. (We can't
1059 * hold onto the original string, since it may not be safe for us
1060 * to free() it.)
1061 */
1062 conn->oauth_token = strdup(request.v1.token);
1063 if (!conn->oauth_token)
1064 {
1065 libpq_append_conn_error(conn, "out of memory");
1066 goto fail;
1067 }
1068
1069 /* short-circuit */
1071 return true;
1072 }
1073
1074 request_copy = malloc(sizeof(*request_copy));
1075 if (!request_copy)
1076 {
1077 libpq_append_conn_error(conn, "out of memory");
1078 goto fail;
1079 }
1080
1082
1085 state->async_ctx = request_copy;
1086
1087 return true;
1088 }
1089
1090 /*
1091 * Failure cases: either we tried to set up a flow and failed, or there
1092 * was no flow to try.
1093 */
1094 if (res < 0)
1096 else
1097 libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
1098
1099fail:
1101 return false;
1102}
static PostgresPollingStatusType run_oauth_flow(PGconn *conn)
static int use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
static void cleanup_oauth_flow(PGconn *conn)
PQauthDataHook_type PQauthDataHook
Definition fe-auth.c:1586
@ PQAUTHDATA_OAUTH_BEARER_TOKEN
Definition libpq-fe.h:202
@ PQAUTHDATA_OAUTH_BEARER_TOKEN_V2
Definition libpq-fe.h:204
#define malloc(a)
PGoauthBearerRequest v1
Definition libpq-fe.h:826
const char * openid_configuration
Definition libpq-fe.h:767
void(* cleanup_async_auth)(PGconn *conn)
Definition libpq-int.h:530

References Assert, pg_conn::async_auth, pg_conn::cleanup_async_auth, cleanup_oauth_flow(), conn, do_cleanup(), fb(), libpq_append_conn_error(), malloc, pg_conn::oauth_discovery_uri, pg_conn::oauth_issuer_id, pg_conn::oauth_scope, pg_conn::oauth_token, PGoauthBearerRequest::openid_configuration, poison_req_v2(), PQAUTHDATA_OAUTH_BEARER_TOKEN, PQAUTHDATA_OAUTH_BEARER_TOKEN_V2, PQauthDataHook, report_flow_error(), run_oauth_flow(), use_builtin_flow(), and PGoauthBearerRequestV2::v1.

Referenced by oauth_exchange().

◆ use_builtin_flow()

static int use_builtin_flow ( PGconn conn,
fe_oauth_state state,
PGoauthBearerRequestV2 request 
)
static

Definition at line 843 of file fe-auth-oauth.c.

844{
845 return 0;
846}

Referenced by setup_token_request().

Variable Documentation

◆ pg_oauth_mech

const pg_fe_sasl_mech pg_oauth_mech
Initial value:
= {
}
static SASLStatus oauth_exchange(void *opaq, bool final, char *input, int inputlen, char **output, int *outputlen)
static bool oauth_channel_bound(void *opaq)
static void oauth_free(void *opaq)
static void * oauth_init(PGconn *conn, const char *password, const char *sasl_mechanism)

Definition at line 47 of file fe-auth-oauth.c.

Referenced by pg_SASL_init(), pqConnectOptions2(), and PQconnectPoll().