LCOV - code coverage report
Current view: top level - src/backend/libpq - auth-oauth.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 0.0 % 290 0
Test Date: 2026-07-03 19:57:34 Functions: 0.0 % 16 0
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 0.0 % 250 0

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * auth-oauth.c
       4                 :             :  *    Server-side implementation of the SASL OAUTHBEARER mechanism.
       5                 :             :  *
       6                 :             :  * See the following RFC for more details:
       7                 :             :  * - RFC 7628: https://datatracker.ietf.org/doc/html/rfc7628
       8                 :             :  *
       9                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
      10                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
      11                 :             :  *
      12                 :             :  * src/backend/libpq/auth-oauth.c
      13                 :             :  *
      14                 :             :  *-------------------------------------------------------------------------
      15                 :             :  */
      16                 :             : #include "postgres.h"
      17                 :             : 
      18                 :             : #include <unistd.h>
      19                 :             : #include <fcntl.h>
      20                 :             : 
      21                 :             : #include "common/oauth-common.h"
      22                 :             : #include "fmgr.h"
      23                 :             : #include "lib/stringinfo.h"
      24                 :             : #include "libpq/auth.h"
      25                 :             : #include "libpq/hba.h"
      26                 :             : #include "libpq/oauth.h"
      27                 :             : #include "libpq/sasl.h"
      28                 :             : #include "miscadmin.h"
      29                 :             : #include "storage/fd.h"
      30                 :             : #include "storage/ipc.h"
      31                 :             : #include "utils/json.h"
      32                 :             : #include "utils/varlena.h"
      33                 :             : 
      34                 :             : /* GUC */
      35                 :             : char       *oauth_validator_libraries_string = NULL;
      36                 :             : 
      37                 :             : static void oauth_get_mechanisms(Port *port, StringInfo buf);
      38                 :             : static void *oauth_init(Port *port, const char *selected_mech, const char *shadow_pass);
      39                 :             : static int  oauth_exchange(void *opaq, const char *input, int inputlen,
      40                 :             :                            char **output, int *outputlen, const char **logdetail);
      41                 :             : 
      42                 :             : static void load_validator_library(const char *libname);
      43                 :             : static void shutdown_validator_library(void *arg);
      44                 :             : static bool check_validator_hba_options(Port *port, const char **logdetail);
      45                 :             : 
      46                 :             : static ValidatorModuleState *validator_module_state;
      47                 :             : static const OAuthValidatorCallbacks *ValidatorCallbacks;
      48                 :             : 
      49                 :             : static MemoryContext ValidatorMemoryContext;
      50                 :             : static List *ValidatorOptions;
      51                 :             : static bool ValidatorOptionsChecked;
      52                 :             : 
      53                 :             : /* Mechanism declaration */
      54                 :             : const pg_be_sasl_mech pg_be_oauth_mech = {
      55                 :             :     .get_mechanisms = oauth_get_mechanisms,
      56                 :             :     .init = oauth_init,
      57                 :             :     .exchange = oauth_exchange,
      58                 :             : 
      59                 :             :     .max_message_length = PG_MAX_AUTH_TOKEN_LENGTH,
      60                 :             : };
      61                 :             : 
      62                 :             : /* Valid states for the oauth_exchange() machine. */
      63                 :             : enum oauth_state
      64                 :             : {
      65                 :             :     OAUTH_STATE_INIT = 0,
      66                 :             :     OAUTH_STATE_ERROR,
      67                 :             :     OAUTH_STATE_ERROR_DISCOVERY,
      68                 :             :     OAUTH_STATE_FINISHED,
      69                 :             : };
      70                 :             : 
      71                 :             : /* Mechanism callback state. */
      72                 :             : struct oauth_ctx
      73                 :             : {
      74                 :             :     enum oauth_state state;
      75                 :             :     Port       *port;
      76                 :             :     const char *issuer;
      77                 :             :     const char *scope;
      78                 :             : };
      79                 :             : 
      80                 :             : static char *sanitize_char(char c);
      81                 :             : static char *parse_kvpairs_for_auth(char **input);
      82                 :             : static void generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen);
      83                 :             : static bool validate(Port *port, const char *auth, const char **logdetail);
      84                 :             : 
      85                 :             : /* Constants seen in an OAUTHBEARER client initial response. */
      86                 :             : #define KVSEP 0x01              /* separator byte for key/value pairs */
      87                 :             : #define AUTH_KEY "auth"           /* key containing the Authorization header */
      88                 :             : #define BEARER_SCHEME "Bearer " /* required header scheme (case-insensitive!) */
      89                 :             : 
      90                 :             : /*
      91                 :             :  * Retrieves the OAUTHBEARER mechanism list (currently a single item).
      92                 :             :  *
      93                 :             :  * For a full description of the API, see libpq/sasl.h.
      94                 :             :  */
      95                 :             : static void
      96                 :           0 : oauth_get_mechanisms(Port *port, StringInfo buf)
      97                 :             : {
      98                 :             :     /* Only OAUTHBEARER is supported. */
      99                 :           0 :     appendStringInfoString(buf, OAUTHBEARER_NAME);
     100                 :           0 :     appendStringInfoChar(buf, '\0');
     101                 :           0 : }
     102                 :             : 
     103                 :             : /*
     104                 :             :  * Initializes mechanism state and loads the configured validator module.
     105                 :             :  *
     106                 :             :  * For a full description of the API, see libpq/sasl.h.
     107                 :             :  */
     108                 :             : static void *
     109                 :           0 : oauth_init(Port *port, const char *selected_mech, const char *shadow_pass)
     110                 :             : {
     111                 :             :     struct oauth_ctx *ctx;
     112                 :             : 
     113         [ #  # ]:           0 :     if (strcmp(selected_mech, OAUTHBEARER_NAME) != 0)
     114         [ #  # ]:           0 :         ereport(ERROR,
     115                 :             :                 errcode(ERRCODE_PROTOCOL_VIOLATION),
     116                 :             :                 errmsg("client selected an invalid SASL authentication mechanism"));
     117                 :             : 
     118                 :             :     /* Save our memory context for later use by client API calls. */
     119                 :           0 :     ValidatorMemoryContext = CurrentMemoryContext;
     120                 :             : 
     121                 :           0 :     ctx = palloc0_object(struct oauth_ctx);
     122                 :             : 
     123                 :           0 :     ctx->state = OAUTH_STATE_INIT;
     124                 :           0 :     ctx->port = port;
     125                 :             : 
     126                 :             :     Assert(port->hba);
     127                 :           0 :     ctx->issuer = port->hba->oauth_issuer;
     128                 :           0 :     ctx->scope = port->hba->oauth_scope;
     129                 :             : 
     130                 :           0 :     load_validator_library(port->hba->oauth_validator);
     131                 :             : 
     132                 :           0 :     return ctx;
     133                 :             : }
     134                 :             : 
     135                 :             : /*
     136                 :             :  * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2). This pulls
     137                 :             :  * apart the client initial response and validates the Bearer token. It also
     138                 :             :  * handles the dummy error response for a failed handshake, as described in
     139                 :             :  * Sec. 3.2.3.
     140                 :             :  *
     141                 :             :  * For a full description of the API, see libpq/sasl.h.
     142                 :             :  */
     143                 :             : static int
     144                 :           0 : oauth_exchange(void *opaq, const char *input, int inputlen,
     145                 :             :                char **output, int *outputlen, const char **logdetail)
     146                 :             : {
     147                 :             :     char       *input_copy;
     148                 :             :     char       *p;
     149                 :             :     char        cbind_flag;
     150                 :             :     char       *auth;
     151                 :             :     int         status;
     152                 :             : 
     153                 :           0 :     struct oauth_ctx *ctx = opaq;
     154                 :             : 
     155                 :           0 :     *output = NULL;
     156                 :           0 :     *outputlen = -1;
     157                 :             : 
     158                 :             :     /*
     159                 :             :      * If the client didn't include an "Initial Client Response" in the
     160                 :             :      * SASLInitialResponse message, send an empty challenge, to which the
     161                 :             :      * client will respond with the same data that usually comes in the
     162                 :             :      * Initial Client Response.
     163                 :             :      */
     164         [ #  # ]:           0 :     if (input == NULL)
     165                 :             :     {
     166                 :             :         Assert(ctx->state == OAUTH_STATE_INIT);
     167                 :             : 
     168                 :           0 :         *output = pstrdup("");
     169                 :           0 :         *outputlen = 0;
     170                 :           0 :         return PG_SASL_EXCHANGE_CONTINUE;
     171                 :             :     }
     172                 :             : 
     173                 :             :     /*
     174                 :             :      * Check that the input length agrees with the string length of the input.
     175                 :             :      */
     176         [ #  # ]:           0 :     if (inputlen == 0)
     177         [ #  # ]:           0 :         ereport(ERROR,
     178                 :             :                 errcode(ERRCODE_PROTOCOL_VIOLATION),
     179                 :             :                 errmsg("malformed OAUTHBEARER message"),
     180                 :             :                 errdetail("The message is empty."));
     181         [ #  # ]:           0 :     if (inputlen != strlen(input))
     182         [ #  # ]:           0 :         ereport(ERROR,
     183                 :             :                 errcode(ERRCODE_PROTOCOL_VIOLATION),
     184                 :             :                 errmsg("malformed OAUTHBEARER message"),
     185                 :             :                 errdetail("Message length does not match input length."));
     186                 :             : 
     187      [ #  #  # ]:           0 :     switch (ctx->state)
     188                 :             :     {
     189                 :           0 :         case OAUTH_STATE_INIT:
     190                 :             :             /* Handle this case below. */
     191                 :           0 :             break;
     192                 :             : 
     193                 :           0 :         case OAUTH_STATE_ERROR:
     194                 :             :         case OAUTH_STATE_ERROR_DISCOVERY:
     195                 :             : 
     196                 :             :             /*
     197                 :             :              * Only one response is valid for the client during authentication
     198                 :             :              * failure: a single kvsep.
     199                 :             :              */
     200   [ #  #  #  # ]:           0 :             if (inputlen != 1 || *input != KVSEP)
     201         [ #  # ]:           0 :                 ereport(ERROR,
     202                 :             :                         errcode(ERRCODE_PROTOCOL_VIOLATION),
     203                 :             :                         errmsg("malformed OAUTHBEARER message"),
     204                 :             :                         errdetail("Client did not send a kvsep response."));
     205                 :             : 
     206                 :             :             /*
     207                 :             :              * The (failed) handshake is now complete. Don't report discovery
     208                 :             :              * requests in the server log unless the log level is high enough.
     209                 :             :              */
     210         [ #  # ]:           0 :             if (ctx->state == OAUTH_STATE_ERROR_DISCOVERY)
     211                 :             :             {
     212         [ #  # ]:           0 :                 ereport(DEBUG1, errmsg("OAuth issuer discovery requested"));
     213                 :             : 
     214                 :           0 :                 ctx->state = OAUTH_STATE_FINISHED;
     215                 :           0 :                 return PG_SASL_EXCHANGE_ABANDONED;
     216                 :             :             }
     217                 :             : 
     218                 :             :             /* We're not in discovery, so this is just a normal auth failure. */
     219                 :           0 :             ctx->state = OAUTH_STATE_FINISHED;
     220                 :           0 :             return PG_SASL_EXCHANGE_FAILURE;
     221                 :             : 
     222                 :           0 :         default:
     223         [ #  # ]:           0 :             elog(ERROR, "invalid OAUTHBEARER exchange state");
     224                 :             :             return PG_SASL_EXCHANGE_FAILURE;
     225                 :             :     }
     226                 :             : 
     227                 :             :     /* Handle the client's initial message. */
     228                 :           0 :     p = input_copy = pstrdup(input);
     229                 :             : 
     230                 :             :     /*
     231                 :             :      * OAUTHBEARER does not currently define a channel binding (so there is no
     232                 :             :      * OAUTHBEARER-PLUS, and we do not accept a 'p' specifier). We accept a
     233                 :             :      * 'y' specifier purely for the remote chance that a future specification
     234                 :             :      * could define one; then future clients can still interoperate with this
     235                 :             :      * server implementation. 'n' is the expected case.
     236                 :             :      */
     237                 :           0 :     cbind_flag = *p;
     238      [ #  #  # ]:           0 :     switch (cbind_flag)
     239                 :             :     {
     240                 :           0 :         case 'p':
     241         [ #  # ]:           0 :             ereport(ERROR,
     242                 :             :                     errcode(ERRCODE_PROTOCOL_VIOLATION),
     243                 :             :                     errmsg("malformed OAUTHBEARER message"),
     244                 :             :                     errdetail("The server does not support channel binding for OAuth, but the client message includes channel binding data."));
     245                 :             :             break;
     246                 :             : 
     247                 :           0 :         case 'y':               /* fall through */
     248                 :             :         case 'n':
     249                 :           0 :             p++;
     250         [ #  # ]:           0 :             if (*p != ',')
     251         [ #  # ]:           0 :                 ereport(ERROR,
     252                 :             :                         errcode(ERRCODE_PROTOCOL_VIOLATION),
     253                 :             :                         errmsg("malformed OAUTHBEARER message"),
     254                 :             :                         errdetail("Comma expected, but found character \"%s\".",
     255                 :             :                                   sanitize_char(*p)));
     256                 :           0 :             p++;
     257                 :           0 :             break;
     258                 :             : 
     259                 :           0 :         default:
     260         [ #  # ]:           0 :             ereport(ERROR,
     261                 :             :                     errcode(ERRCODE_PROTOCOL_VIOLATION),
     262                 :             :                     errmsg("malformed OAUTHBEARER message"),
     263                 :             :                     errdetail("Unexpected channel-binding flag \"%s\".",
     264                 :             :                               sanitize_char(cbind_flag)));
     265                 :             :     }
     266                 :             : 
     267                 :             :     /*
     268                 :             :      * Forbid optional authzid (authorization identity).  We don't support it.
     269                 :             :      */
     270         [ #  # ]:           0 :     if (*p == 'a')
     271         [ #  # ]:           0 :         ereport(ERROR,
     272                 :             :                 errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     273                 :             :                 errmsg("client uses authorization identity, but it is not supported"));
     274         [ #  # ]:           0 :     if (*p != ',')
     275         [ #  # ]:           0 :         ereport(ERROR,
     276                 :             :                 errcode(ERRCODE_PROTOCOL_VIOLATION),
     277                 :             :                 errmsg("malformed OAUTHBEARER message"),
     278                 :             :                 errdetail("Unexpected attribute \"%s\" in client-first-message.",
     279                 :             :                           sanitize_char(*p)));
     280                 :           0 :     p++;
     281                 :             : 
     282                 :             :     /* All remaining fields are separated by the RFC's kvsep (\x01). */
     283         [ #  # ]:           0 :     if (*p != KVSEP)
     284         [ #  # ]:           0 :         ereport(ERROR,
     285                 :             :                 errcode(ERRCODE_PROTOCOL_VIOLATION),
     286                 :             :                 errmsg("malformed OAUTHBEARER message"),
     287                 :             :                 errdetail("Key-value separator expected, but found character \"%s\".",
     288                 :             :                           sanitize_char(*p)));
     289                 :           0 :     p++;
     290                 :             : 
     291                 :           0 :     auth = parse_kvpairs_for_auth(&p);
     292         [ #  # ]:           0 :     if (!auth)
     293         [ #  # ]:           0 :         ereport(ERROR,
     294                 :             :                 errcode(ERRCODE_PROTOCOL_VIOLATION),
     295                 :             :                 errmsg("malformed OAUTHBEARER message"),
     296                 :             :                 errdetail("Message does not contain an auth value."));
     297                 :             : 
     298                 :             :     /* We should be at the end of our message. */
     299         [ #  # ]:           0 :     if (*p)
     300         [ #  # ]:           0 :         ereport(ERROR,
     301                 :             :                 errcode(ERRCODE_PROTOCOL_VIOLATION),
     302                 :             :                 errmsg("malformed OAUTHBEARER message"),
     303                 :             :                 errdetail("Message contains additional data after the final terminator."));
     304                 :             : 
     305                 :             :     /*
     306                 :             :      * Make sure all custom HBA options are understood by the validator before
     307                 :             :      * continuing, since we couldn't check them during server start/reload.
     308                 :             :      */
     309         [ #  # ]:           0 :     if (!check_validator_hba_options(ctx->port, logdetail))
     310                 :             :     {
     311                 :           0 :         ctx->state = OAUTH_STATE_FINISHED;
     312                 :           0 :         return PG_SASL_EXCHANGE_FAILURE;
     313                 :             :     }
     314                 :             : 
     315         [ #  # ]:           0 :     if (auth[0] == '\0')
     316                 :             :     {
     317                 :             :         /*
     318                 :             :          * An empty auth value represents a discovery request; the client
     319                 :             :          * expects it to fail.  Skip validation entirely and move directly to
     320                 :             :          * the error response.
     321                 :             :          */
     322                 :           0 :         generate_error_response(ctx, output, outputlen);
     323                 :             : 
     324                 :           0 :         ctx->state = OAUTH_STATE_ERROR_DISCOVERY;
     325                 :           0 :         status = PG_SASL_EXCHANGE_CONTINUE;
     326                 :             :     }
     327         [ #  # ]:           0 :     else if (!validate(ctx->port, auth, logdetail))
     328                 :             :     {
     329                 :           0 :         generate_error_response(ctx, output, outputlen);
     330                 :             : 
     331                 :           0 :         ctx->state = OAUTH_STATE_ERROR;
     332                 :           0 :         status = PG_SASL_EXCHANGE_CONTINUE;
     333                 :             :     }
     334                 :             :     else
     335                 :             :     {
     336                 :           0 :         ctx->state = OAUTH_STATE_FINISHED;
     337                 :           0 :         status = PG_SASL_EXCHANGE_SUCCESS;
     338                 :             :     }
     339                 :             : 
     340                 :             :     /* Don't let extra copies of the bearer token hang around. */
     341                 :           0 :     explicit_bzero(input_copy, inputlen);
     342                 :             : 
     343                 :           0 :     return status;
     344                 :             : }
     345                 :             : 
     346                 :             : /*
     347                 :             :  * Convert an arbitrary byte to printable form.  For error messages.
     348                 :             :  *
     349                 :             :  * If it's a printable ASCII character, print it as a single character.
     350                 :             :  * otherwise, print it in hex.
     351                 :             :  *
     352                 :             :  * The returned pointer points to a static buffer.
     353                 :             :  */
     354                 :             : static char *
     355                 :           0 : sanitize_char(char c)
     356                 :             : {
     357                 :             :     static char buf[5];
     358                 :             : 
     359   [ #  #  #  # ]:           0 :     if (c >= 0x21 && c <= 0x7E)
     360                 :           0 :         snprintf(buf, sizeof(buf), "'%c'", c);
     361                 :             :     else
     362                 :           0 :         snprintf(buf, sizeof(buf), "0x%02x", (unsigned char) c);
     363                 :           0 :     return buf;
     364                 :             : }
     365                 :             : 
     366                 :             : /*
     367                 :             :  * Performs syntactic validation of a key and value from the initial client
     368                 :             :  * response. (Semantic validation of interesting values must be performed
     369                 :             :  * later.)
     370                 :             :  */
     371                 :             : static void
     372                 :           0 : validate_kvpair(const char *key, const char *val)
     373                 :             : {
     374                 :             :     /*-----
     375                 :             :      * From Sec 3.1:
     376                 :             :      *     key            = 1*(ALPHA)
     377                 :             :      */
     378                 :             :     static const char *key_allowed_set =
     379                 :             :         "abcdefghijklmnopqrstuvwxyz"
     380                 :             :         "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
     381                 :             : 
     382                 :             :     size_t      span;
     383                 :             : 
     384         [ #  # ]:           0 :     if (!key[0])
     385         [ #  # ]:           0 :         ereport(ERROR,
     386                 :             :                 errcode(ERRCODE_PROTOCOL_VIOLATION),
     387                 :             :                 errmsg("malformed OAUTHBEARER message"),
     388                 :             :                 errdetail("Message contains an empty key name."));
     389                 :             : 
     390                 :           0 :     span = strspn(key, key_allowed_set);
     391         [ #  # ]:           0 :     if (key[span] != '\0')
     392         [ #  # ]:           0 :         ereport(ERROR,
     393                 :             :                 errcode(ERRCODE_PROTOCOL_VIOLATION),
     394                 :             :                 errmsg("malformed OAUTHBEARER message"),
     395                 :             :                 errdetail("Message contains an invalid key name."));
     396                 :             : 
     397                 :             :     /*-----
     398                 :             :      * From Sec 3.1:
     399                 :             :      *     value          = *(VCHAR / SP / HTAB / CR / LF )
     400                 :             :      *
     401                 :             :      * The VCHAR (visible character) class is large; a loop is more
     402                 :             :      * straightforward than strspn().
     403                 :             :      */
     404         [ #  # ]:           0 :     for (; *val; ++val)
     405                 :             :     {
     406   [ #  #  #  # ]:           0 :         if (0x21 <= *val && *val <= 0x7E)
     407                 :           0 :             continue;           /* VCHAR */
     408                 :             : 
     409         [ #  # ]:           0 :         switch (*val)
     410                 :             :         {
     411                 :           0 :             case ' ':
     412                 :             :             case '\t':
     413                 :             :             case '\r':
     414                 :             :             case '\n':
     415                 :           0 :                 continue;       /* SP, HTAB, CR, LF */
     416                 :             : 
     417                 :           0 :             default:
     418         [ #  # ]:           0 :                 ereport(ERROR,
     419                 :             :                         errcode(ERRCODE_PROTOCOL_VIOLATION),
     420                 :             :                         errmsg("malformed OAUTHBEARER message"),
     421                 :             :                         errdetail("Message contains an invalid value."));
     422                 :             :         }
     423                 :             :     }
     424                 :           0 : }
     425                 :             : 
     426                 :             : /*
     427                 :             :  * Consumes all kvpairs in an OAUTHBEARER exchange message. If the "auth" key is
     428                 :             :  * found, its value is returned.
     429                 :             :  */
     430                 :             : static char *
     431                 :           0 : parse_kvpairs_for_auth(char **input)
     432                 :             : {
     433                 :           0 :     char       *pos = *input;
     434                 :           0 :     char       *auth = NULL;
     435                 :             : 
     436                 :             :     /*----
     437                 :             :      * The relevant ABNF, from Sec. 3.1:
     438                 :             :      *
     439                 :             :      *     kvsep          = %x01
     440                 :             :      *     key            = 1*(ALPHA)
     441                 :             :      *     value          = *(VCHAR / SP / HTAB / CR / LF )
     442                 :             :      *     kvpair         = key "=" value kvsep
     443                 :             :      *   ;;gs2-header     = See RFC 5801
     444                 :             :      *     client-resp    = (gs2-header kvsep *kvpair kvsep) / kvsep
     445                 :             :      *
     446                 :             :      * By the time we reach this code, the gs2-header and initial kvsep have
     447                 :             :      * already been validated. We start at the beginning of the first kvpair.
     448                 :             :      */
     449                 :             : 
     450         [ #  # ]:           0 :     while (*pos)
     451                 :             :     {
     452                 :             :         char       *end;
     453                 :             :         char       *sep;
     454                 :             :         char       *key;
     455                 :             :         char       *value;
     456                 :             : 
     457                 :             :         /*
     458                 :             :          * Find the end of this kvpair. Note that input is null-terminated by
     459                 :             :          * the SASL code, so the strchr() is bounded.
     460                 :             :          */
     461                 :           0 :         end = strchr(pos, KVSEP);
     462         [ #  # ]:           0 :         if (!end)
     463         [ #  # ]:           0 :             ereport(ERROR,
     464                 :             :                     errcode(ERRCODE_PROTOCOL_VIOLATION),
     465                 :             :                     errmsg("malformed OAUTHBEARER message"),
     466                 :             :                     errdetail("Message contains an unterminated key/value pair."));
     467                 :           0 :         *end = '\0';
     468                 :             : 
     469         [ #  # ]:           0 :         if (pos == end)
     470                 :             :         {
     471                 :             :             /* Empty kvpair, signifying the end of the list. */
     472                 :           0 :             *input = pos + 1;
     473                 :           0 :             return auth;
     474                 :             :         }
     475                 :             : 
     476                 :             :         /*
     477                 :             :          * Find the end of the key name.
     478                 :             :          */
     479                 :           0 :         sep = strchr(pos, '=');
     480         [ #  # ]:           0 :         if (!sep)
     481         [ #  # ]:           0 :             ereport(ERROR,
     482                 :             :                     errcode(ERRCODE_PROTOCOL_VIOLATION),
     483                 :             :                     errmsg("malformed OAUTHBEARER message"),
     484                 :             :                     errdetail("Message contains a key without a value."));
     485                 :           0 :         *sep = '\0';
     486                 :             : 
     487                 :             :         /* Both key and value are now safely terminated. */
     488                 :           0 :         key = pos;
     489                 :           0 :         value = sep + 1;
     490                 :           0 :         validate_kvpair(key, value);
     491                 :             : 
     492         [ #  # ]:           0 :         if (strcmp(key, AUTH_KEY) == 0)
     493                 :             :         {
     494         [ #  # ]:           0 :             if (auth)
     495         [ #  # ]:           0 :                 ereport(ERROR,
     496                 :             :                         errcode(ERRCODE_PROTOCOL_VIOLATION),
     497                 :             :                         errmsg("malformed OAUTHBEARER message"),
     498                 :             :                         errdetail("Message contains multiple auth values."));
     499                 :             : 
     500                 :           0 :             auth = value;
     501                 :             :         }
     502                 :             :         else
     503                 :             :         {
     504                 :             :             /*
     505                 :             :              * The RFC also defines the host and port keys, but they are not
     506                 :             :              * required for OAUTHBEARER and we do not use them. Also, per Sec.
     507                 :             :              * 3.1, any key/value pairs we don't recognize must be ignored.
     508                 :             :              */
     509                 :             :         }
     510                 :             : 
     511                 :             :         /* Move to the next pair. */
     512                 :           0 :         pos = end + 1;
     513                 :             :     }
     514                 :             : 
     515         [ #  # ]:           0 :     ereport(ERROR,
     516                 :             :             errcode(ERRCODE_PROTOCOL_VIOLATION),
     517                 :             :             errmsg("malformed OAUTHBEARER message"),
     518                 :             :             errdetail("Message did not contain a final terminator."));
     519                 :             : 
     520                 :             :     pg_unreachable();
     521                 :             :     return NULL;
     522                 :             : }
     523                 :             : 
     524                 :             : /*
     525                 :             :  * Builds the JSON response for failed authentication (RFC 7628, Sec. 3.2.2).
     526                 :             :  * This contains the required scopes for entry and a pointer to the OAuth/OpenID
     527                 :             :  * discovery document, which the client may use to conduct its OAuth flow.
     528                 :             :  */
     529                 :             : static void
     530                 :           0 : generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen)
     531                 :             : {
     532                 :             :     StringInfoData buf;
     533                 :             :     StringInfoData issuer;
     534                 :             : 
     535                 :             :     /*
     536                 :             :      * The admin needs to set an issuer and scope for OAuth to work. There's
     537                 :             :      * not really a way to hide this from the user, either, because we can't
     538                 :             :      * choose a "default" issuer, so be honest in the failure message. (In
     539                 :             :      * practice such configurations are rejected during HBA parsing.)
     540                 :             :      */
     541   [ #  #  #  # ]:           0 :     if (!ctx->issuer || !ctx->scope)
     542         [ #  # ]:           0 :         ereport(FATAL,
     543                 :             :                 errcode(ERRCODE_INTERNAL_ERROR),
     544                 :             :                 errmsg("OAuth is not properly configured for this user"),
     545                 :             :                 errdetail_log("The options \"issuer\" and \"scope\" must be set in pg_hba.conf."));
     546                 :             : 
     547                 :             :     /*
     548                 :             :      * Build a default .well-known URI based on our issuer, unless the HBA has
     549                 :             :      * already provided one.
     550                 :             :      */
     551                 :           0 :     initStringInfo(&issuer);
     552                 :           0 :     appendStringInfoString(&issuer, ctx->issuer);
     553         [ #  # ]:           0 :     if (strstr(ctx->issuer, "/.well-known/") == NULL)
     554                 :           0 :         appendStringInfoString(&issuer, "/.well-known/openid-configuration");
     555                 :             : 
     556                 :           0 :     initStringInfo(&buf);
     557                 :             : 
     558                 :             :     /*
     559                 :             :      * Escaping the string here is belt-and-suspenders defensive programming
     560                 :             :      * since escapable characters aren't valid in either the issuer URI or the
     561                 :             :      * scope list, but the HBA doesn't enforce that yet.
     562                 :             :      */
     563                 :           0 :     appendStringInfoString(&buf, "{ \"status\": \"invalid_token\", ");
     564                 :             : 
     565                 :           0 :     appendStringInfoString(&buf, "\"openid-configuration\": ");
     566                 :           0 :     escape_json(&buf, issuer.data);
     567                 :           0 :     pfree(issuer.data);
     568                 :             : 
     569                 :           0 :     appendStringInfoString(&buf, ", \"scope\": ");
     570                 :           0 :     escape_json(&buf, ctx->scope);
     571                 :             : 
     572                 :           0 :     appendStringInfoString(&buf, " }");
     573                 :             : 
     574                 :           0 :     *output = buf.data;
     575                 :           0 :     *outputlen = buf.len;
     576                 :           0 : }
     577                 :             : 
     578                 :             : /*-----
     579                 :             :  * Validates the provided Authorization header and returns the token from
     580                 :             :  * within it. NULL is returned on validation failure.
     581                 :             :  *
     582                 :             :  * Only Bearer tokens are accepted. The ABNF is defined in RFC 6750, Sec.
     583                 :             :  * 2.1:
     584                 :             :  *
     585                 :             :  *      b64token    = 1*( ALPHA / DIGIT /
     586                 :             :  *                        "-" / "." / "_" / "~" / "+" / "/" ) *"="
     587                 :             :  *      credentials = "Bearer" 1*SP b64token
     588                 :             :  *
     589                 :             :  * The "credentials" construction is what we receive in our auth value.
     590                 :             :  *
     591                 :             :  * Since that spec is subordinate to HTTP (i.e. the HTTP Authorization
     592                 :             :  * header format; RFC 9110 Sec. 11), the "Bearer" scheme string must be
     593                 :             :  * compared case-insensitively. (This is not mentioned in RFC 6750, but the
     594                 :             :  * OAUTHBEARER spec points it out: RFC 7628 Sec. 4.)
     595                 :             :  *
     596                 :             :  * Invalid formats are technically a protocol violation, but we shouldn't
     597                 :             :  * reflect any information about the sensitive Bearer token back to the
     598                 :             :  * client; log at COMMERROR instead.
     599                 :             :  */
     600                 :             : static const char *
     601                 :           0 : validate_token_format(const char *header)
     602                 :             : {
     603                 :             :     size_t      span;
     604                 :             :     const char *token;
     605                 :             :     static const char *const b64token_allowed_set =
     606                 :             :         "abcdefghijklmnopqrstuvwxyz"
     607                 :             :         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     608                 :             :         "0123456789-._~+/";
     609                 :             : 
     610                 :             :     /* Missing auth headers should be handled by the caller. */
     611                 :             :     Assert(header);
     612                 :             :     /* Empty auth (discovery) should be handled before calling validate(). */
     613                 :             :     Assert(header[0] != '\0');
     614                 :             : 
     615         [ #  # ]:           0 :     if (pg_strncasecmp(header, BEARER_SCHEME, strlen(BEARER_SCHEME)))
     616                 :             :     {
     617         [ #  # ]:           0 :         ereport(COMMERROR,
     618                 :             :                 errcode(ERRCODE_PROTOCOL_VIOLATION),
     619                 :             :                 errmsg("malformed OAuth bearer token"),
     620                 :             :                 errdetail_log("Client response indicated a non-Bearer authentication scheme."));
     621                 :           0 :         return NULL;
     622                 :             :     }
     623                 :             : 
     624                 :             :     /* Pull the bearer token out of the auth value. */
     625                 :           0 :     token = header + strlen(BEARER_SCHEME);
     626                 :             : 
     627                 :             :     /* Swallow any additional spaces. */
     628         [ #  # ]:           0 :     while (*token == ' ')
     629                 :           0 :         token++;
     630                 :             : 
     631                 :             :     /* Tokens must not be empty. */
     632         [ #  # ]:           0 :     if (!*token)
     633                 :             :     {
     634         [ #  # ]:           0 :         ereport(COMMERROR,
     635                 :             :                 errcode(ERRCODE_PROTOCOL_VIOLATION),
     636                 :             :                 errmsg("malformed OAuth bearer token"),
     637                 :             :                 errdetail_log("Bearer token is empty."));
     638                 :           0 :         return NULL;
     639                 :             :     }
     640                 :             : 
     641                 :             :     /*
     642                 :             :      * Make sure the token contains only allowed characters. Tokens may end
     643                 :             :      * with any number of '=' characters.
     644                 :             :      */
     645                 :           0 :     span = strspn(token, b64token_allowed_set);
     646         [ #  # ]:           0 :     while (token[span] == '=')
     647                 :           0 :         span++;
     648                 :             : 
     649         [ #  # ]:           0 :     if (token[span] != '\0')
     650                 :             :     {
     651                 :             :         /*
     652                 :             :          * This error message could be more helpful by printing the
     653                 :             :          * problematic character(s), but that'd be a bit like printing a piece
     654                 :             :          * of someone's password into the logs.
     655                 :             :          */
     656         [ #  # ]:           0 :         ereport(COMMERROR,
     657                 :             :                 errcode(ERRCODE_PROTOCOL_VIOLATION),
     658                 :             :                 errmsg("malformed OAuth bearer token"),
     659                 :             :                 errdetail_log("Bearer token is not in the correct format."));
     660                 :           0 :         return NULL;
     661                 :             :     }
     662                 :             : 
     663                 :           0 :     return token;
     664                 :             : }
     665                 :             : 
     666                 :             : /*
     667                 :             :  * Checks that the "auth" kvpair in the client response contains a syntactically
     668                 :             :  * valid Bearer token, then passes it along to the loaded validator module for
     669                 :             :  * authorization. Returns true if validation succeeds.
     670                 :             :  */
     671                 :             : static bool
     672                 :           0 : validate(Port *port, const char *auth, const char **logdetail)
     673                 :             : {
     674                 :             :     int         map_status;
     675                 :             :     ValidatorModuleResult *ret;
     676                 :             :     const char *token;
     677                 :             :     bool        status;
     678                 :             : 
     679                 :             :     /* Ensure that we have a correct token to validate */
     680         [ #  # ]:           0 :     if (!(token = validate_token_format(auth)))
     681                 :           0 :         return false;
     682                 :             : 
     683                 :             :     /*
     684                 :             :      * Ensure that we have a validation library loaded, this should always be
     685                 :             :      * the case and an error here is indicative of a bug.
     686                 :             :      */
     687   [ #  #  #  # ]:           0 :     if (!ValidatorCallbacks || !ValidatorCallbacks->validate_cb)
     688         [ #  # ]:           0 :         ereport(FATAL,
     689                 :             :                 errcode(ERRCODE_INTERNAL_ERROR),
     690                 :             :                 errmsg("validation of OAuth token requested without a validator loaded"));
     691                 :             : 
     692                 :             :     /* Call the validation function from the validator module */
     693                 :           0 :     ret = palloc0_object(ValidatorModuleResult);
     694         [ #  # ]:           0 :     if (!ValidatorCallbacks->validate_cb(validator_module_state, token,
     695                 :           0 :                                          port->user_name, ret))
     696                 :             :     {
     697   [ #  #  #  # ]:           0 :         ereport(WARNING,
     698                 :             :                 errcode(ERRCODE_INTERNAL_ERROR),
     699                 :             :                 errmsg("internal error in OAuth validator module"),
     700                 :             :                 ret->error_detail ? errdetail_log("%s", ret->error_detail) : 0);
     701                 :             : 
     702                 :           0 :         *logdetail = ret->error_detail;
     703                 :           0 :         return false;
     704                 :             :     }
     705                 :             : 
     706                 :             :     /*
     707                 :             :      * Log any authentication results even if the token isn't authorized; it
     708                 :             :      * might be useful for auditing or troubleshooting.
     709                 :             :      */
     710         [ #  # ]:           0 :     if (ret->authn_id)
     711                 :           0 :         set_authn_id(port, ret->authn_id);
     712                 :             : 
     713         [ #  # ]:           0 :     if (!ret->authorized)
     714                 :             :     {
     715         [ #  # ]:           0 :         if (ret->error_detail)
     716                 :           0 :             *logdetail = ret->error_detail;
     717                 :             :         else
     718                 :           0 :             *logdetail = _("Validator failed to authorize the provided token.");
     719                 :             : 
     720                 :           0 :         status = false;
     721                 :           0 :         goto cleanup;
     722                 :             :     }
     723                 :             : 
     724         [ #  # ]:           0 :     if (port->hba->oauth_skip_usermap)
     725                 :             :     {
     726                 :             :         /*
     727                 :             :          * If the validator is our authorization authority, we're done.
     728                 :             :          * Authentication may or may not have been performed depending on the
     729                 :             :          * validator implementation; all that matters is that the validator
     730                 :             :          * says the user can log in with the target role.
     731                 :             :          */
     732                 :           0 :         status = true;
     733                 :           0 :         goto cleanup;
     734                 :             :     }
     735                 :             : 
     736                 :             :     /* Make sure the validator authenticated the user. */
     737   [ #  #  #  # ]:           0 :     if (ret->authn_id == NULL || ret->authn_id[0] == '\0')
     738                 :             :     {
     739                 :           0 :         *logdetail = _("Validator provided no identity.");
     740                 :             : 
     741                 :           0 :         status = false;
     742                 :           0 :         goto cleanup;
     743                 :             :     }
     744                 :             : 
     745                 :             :     /* Finally, check the user map. */
     746                 :           0 :     map_status = check_usermap(port->hba->usermap, port->user_name,
     747                 :             :                                MyClientConnectionInfo.authn_id, false);
     748                 :           0 :     status = (map_status == STATUS_OK);
     749                 :             : 
     750                 :           0 : cleanup:
     751                 :             : 
     752                 :             :     /*
     753                 :             :      * Clear and free the validation result from the validator module once
     754                 :             :      * we're done with it.
     755                 :             :      */
     756         [ #  # ]:           0 :     if (ret->authn_id != NULL)
     757                 :           0 :         pfree(ret->authn_id);
     758                 :           0 :     pfree(ret);
     759                 :             : 
     760                 :           0 :     return status;
     761                 :             : }
     762                 :             : 
     763                 :             : /*
     764                 :             :  * load_validator_library
     765                 :             :  *
     766                 :             :  * Load the configured validator library in order to perform token validation.
     767                 :             :  * There is no built-in fallback since validation is implementation specific. If
     768                 :             :  * no validator library is configured, or if it fails to load, then error out
     769                 :             :  * since token validation won't be possible.
     770                 :             :  */
     771                 :             : static void
     772                 :           0 : load_validator_library(const char *libname)
     773                 :             : {
     774                 :             :     OAuthValidatorModuleInit validator_init;
     775                 :             :     MemoryContextCallback *mcb;
     776                 :             : 
     777                 :             :     /*
     778                 :             :      * The presence, and validity, of libname has already been established by
     779                 :             :      * check_oauth_validator so we don't need to perform more than Assert
     780                 :             :      * level checking here.
     781                 :             :      */
     782                 :             :     Assert(libname && *libname);
     783                 :             : 
     784                 :           0 :     validator_init = (OAuthValidatorModuleInit)
     785                 :           0 :         load_external_function(libname, "_PG_oauth_validator_module_init",
     786                 :             :                                false, NULL);
     787                 :             : 
     788                 :             :     /*
     789                 :             :      * The validator init function is required since it will set the callbacks
     790                 :             :      * for the validator library.
     791                 :             :      */
     792         [ #  # ]:           0 :     if (validator_init == NULL)
     793         [ #  # ]:           0 :         ereport(ERROR,
     794                 :             :                 errmsg("OAuth validator module \"%s\" must define the symbol \"%s\"",
     795                 :             :                        libname, "_PG_oauth_validator_module_init"));
     796                 :             : 
     797                 :           0 :     ValidatorCallbacks = (*validator_init) ();
     798                 :             :     Assert(ValidatorCallbacks);
     799                 :             : 
     800                 :             :     /*
     801                 :             :      * Check the magic number, to protect against break-glass scenarios where
     802                 :             :      * the ABI must change within a major version. load_external_function()
     803                 :             :      * already checks for compatibility across major versions.
     804                 :             :      */
     805         [ #  # ]:           0 :     if (ValidatorCallbacks->magic != PG_OAUTH_VALIDATOR_MAGIC)
     806         [ #  # ]:           0 :         ereport(ERROR,
     807                 :             :                 errmsg("OAuth validator module \"%s\": magic number mismatch",
     808                 :             :                        libname),
     809                 :             :                 errdetail("Server has magic number 0x%08X, module has 0x%08X.",
     810                 :             :                           PG_OAUTH_VALIDATOR_MAGIC, ValidatorCallbacks->magic));
     811                 :             : 
     812                 :             :     /*
     813                 :             :      * Make sure all required callbacks are present in the ValidatorCallbacks
     814                 :             :      * structure. Right now only the validation callback is required.
     815                 :             :      */
     816         [ #  # ]:           0 :     if (ValidatorCallbacks->validate_cb == NULL)
     817         [ #  # ]:           0 :         ereport(ERROR,
     818                 :             :                 errmsg("OAuth validator module \"%s\" must provide a \"%s\" callback",
     819                 :             :                        libname, "validate_cb"));
     820                 :             : 
     821                 :             :     /* Allocate memory for validator library private state data */
     822                 :           0 :     validator_module_state = palloc0_object(ValidatorModuleState);
     823                 :           0 :     validator_module_state->sversion = PG_VERSION_NUM;
     824                 :             : 
     825         [ #  # ]:           0 :     if (ValidatorCallbacks->startup_cb != NULL)
     826                 :           0 :         ValidatorCallbacks->startup_cb(validator_module_state);
     827                 :             : 
     828                 :             :     /* Shut down the library before cleaning up its state. */
     829                 :           0 :     mcb = palloc0_object(MemoryContextCallback);
     830                 :           0 :     mcb->func = shutdown_validator_library;
     831                 :             : 
     832                 :           0 :     MemoryContextRegisterResetCallback(CurrentMemoryContext, mcb);
     833                 :           0 : }
     834                 :             : 
     835                 :             : /*
     836                 :             :  * Call the validator module's shutdown callback, if one is provided. This is
     837                 :             :  * invoked during memory context reset.
     838                 :             :  */
     839                 :             : static void
     840                 :           0 : shutdown_validator_library(void *arg)
     841                 :             : {
     842         [ #  # ]:           0 :     if (ValidatorCallbacks->shutdown_cb != NULL)
     843                 :           0 :         ValidatorCallbacks->shutdown_cb(validator_module_state);
     844                 :             : 
     845                 :             :     /* The backing memory for this is about to disappear. */
     846                 :           0 :     ValidatorOptions = NIL;
     847                 :           0 : }
     848                 :             : 
     849                 :             : /*
     850                 :             :  * Ensure an OAuth validator named in the HBA is permitted by the configuration.
     851                 :             :  *
     852                 :             :  * If the validator is currently unset and exactly one library is declared in
     853                 :             :  * oauth_validator_libraries, then that library will be used as the validator.
     854                 :             :  * Otherwise the name must be present in the list of oauth_validator_libraries.
     855                 :             :  */
     856                 :             : bool
     857                 :           0 : check_oauth_validator(HbaLine *hbaline, int elevel, char **err_msg)
     858                 :             : {
     859                 :           0 :     int         line_num = hbaline->linenumber;
     860                 :           0 :     const char *file_name = hbaline->sourcefile;
     861                 :             :     char       *rawstring;
     862                 :           0 :     List       *elemlist = NIL;
     863                 :             : 
     864                 :           0 :     *err_msg = NULL;
     865                 :             : 
     866         [ #  # ]:           0 :     if (oauth_validator_libraries_string[0] == '\0')
     867                 :             :     {
     868         [ #  # ]:           0 :         ereport(elevel,
     869                 :             :                 errcode(ERRCODE_CONFIG_FILE_ERROR),
     870                 :             :                 errmsg("parameter \"%s\" must be set for authentication method \"%s\"",
     871                 :             :                        "oauth_validator_libraries", "oauth"),
     872                 :             :                 errcontext("line %d of configuration file \"%s\"",
     873                 :             :                            line_num, file_name));
     874                 :           0 :         *err_msg = psprintf("parameter \"%s\" must be set for authentication method \"%s\"",
     875                 :             :                             "oauth_validator_libraries", "oauth");
     876                 :           0 :         return false;
     877                 :             :     }
     878                 :             : 
     879                 :             :     /* SplitDirectoriesString needs a modifiable copy */
     880                 :           0 :     rawstring = pstrdup(oauth_validator_libraries_string);
     881                 :             : 
     882         [ #  # ]:           0 :     if (!SplitDirectoriesString(rawstring, ',', &elemlist))
     883                 :             :     {
     884                 :             :         /* syntax error in list */
     885         [ #  # ]:           0 :         ereport(elevel,
     886                 :             :                 errcode(ERRCODE_CONFIG_FILE_ERROR),
     887                 :             :                 errmsg("invalid list syntax in parameter \"%s\"",
     888                 :             :                        "oauth_validator_libraries"));
     889                 :           0 :         *err_msg = psprintf("invalid list syntax in parameter \"%s\"",
     890                 :             :                             "oauth_validator_libraries");
     891                 :           0 :         goto done;
     892                 :             :     }
     893                 :             : 
     894         [ #  # ]:           0 :     if (!hbaline->oauth_validator)
     895                 :             :     {
     896         [ #  # ]:           0 :         if (elemlist->length == 1)
     897                 :             :         {
     898                 :           0 :             hbaline->oauth_validator = pstrdup(linitial(elemlist));
     899                 :           0 :             goto done;
     900                 :             :         }
     901                 :             : 
     902         [ #  # ]:           0 :         ereport(elevel,
     903                 :             :                 errcode(ERRCODE_CONFIG_FILE_ERROR),
     904                 :             :                 errmsg("authentication method \"oauth\" requires option \"validator\" to be set when \"%s\" contains multiple options",
     905                 :             :                        "oauth_validator_libraries"),
     906                 :             :                 errcontext("line %d of configuration file \"%s\"",
     907                 :             :                            line_num, file_name));
     908                 :           0 :         *err_msg = psprintf("authentication method \"oauth\" requires option \"validator\" to be set when \"%s\" contains multiple options",
     909                 :             :                             "oauth_validator_libraries");
     910                 :           0 :         goto done;
     911                 :             :     }
     912                 :             : 
     913   [ #  #  #  #  :           0 :     foreach_ptr(char, allowed, elemlist)
                   #  # ]
     914                 :             :     {
     915         [ #  # ]:           0 :         if (strcmp(allowed, hbaline->oauth_validator) == 0)
     916                 :           0 :             goto done;
     917                 :             :     }
     918                 :             : 
     919         [ #  # ]:           0 :     ereport(elevel,
     920                 :             :             errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     921                 :             :             errmsg("validator \"%s\" is not permitted by \"%s\"",
     922                 :             :                    hbaline->oauth_validator, "oauth_validator_libraries"),
     923                 :             :             errcontext("line %d of configuration file \"%s\"",
     924                 :             :                        line_num, file_name));
     925                 :           0 :     *err_msg = psprintf("validator \"%s\" is not permitted by \"%s\"",
     926                 :             :                         hbaline->oauth_validator, "oauth_validator_libraries");
     927                 :             : 
     928                 :           0 : done:
     929                 :           0 :     list_free_deep(elemlist);
     930                 :           0 :     pfree(rawstring);
     931                 :             : 
     932                 :           0 :     return (*err_msg == NULL);
     933                 :             : }
     934                 :             : 
     935                 :             : /*
     936                 :             :  * Client APIs for validator implementations
     937                 :             :  *
     938                 :             :  * Since we're currently not threaded, we only allow one validator in the
     939                 :             :  * process at a time. So we can make use of globals for now instead of looking
     940                 :             :  * up information using the state pointer. We probably shouldn't assume that the
     941                 :             :  * module hasn't temporarily changed memory contexts on us, though; functions
     942                 :             :  * here should defensively use an appropriate context when making global
     943                 :             :  * allocations.
     944                 :             :  */
     945                 :             : 
     946                 :             : /*
     947                 :             :  * Adds to the list of allowed validator.* HBA options. Used during the
     948                 :             :  * startup_cb.
     949                 :             :  */
     950                 :             : void
     951                 :           0 : RegisterOAuthHBAOptions(ValidatorModuleState *state, int num,
     952                 :             :                         const char *opts[])
     953                 :             : {
     954                 :             :     MemoryContext oldcontext;
     955                 :             : 
     956         [ #  # ]:           0 :     if (!state)
     957                 :             :     {
     958                 :             :         Assert(false);
     959                 :           0 :         return;
     960                 :             :     }
     961                 :             : 
     962                 :           0 :     oldcontext = MemoryContextSwitchTo(ValidatorMemoryContext);
     963                 :             : 
     964         [ #  # ]:           0 :     for (int i = 0; i < num; i++)
     965                 :             :     {
     966         [ #  # ]:           0 :         if (!valid_oauth_hba_option_name(opts[i]))
     967                 :             :         {
     968                 :             :             /*
     969                 :             :              * The user can't set this option in the HBA, so GetOAuthHBAOption
     970                 :             :              * would always return NULL.
     971                 :             :              */
     972         [ #  # ]:           0 :             ereport(WARNING,
     973                 :             :                     errmsg("HBA option name \"%s\" is invalid and will be ignored",
     974                 :             :                            opts[i]),
     975                 :             :             /* translator: the second %s is a function name */
     976                 :             :                     errcontext("validator module \"%s\", in call to %s",
     977                 :             :                                MyProcPort->hba->oauth_validator,
     978                 :             :                                "RegisterOAuthHBAOptions"));
     979                 :           0 :             continue;
     980                 :             :         }
     981                 :             : 
     982                 :           0 :         ValidatorOptions = lappend(ValidatorOptions, pstrdup(opts[i]));
     983                 :             :     }
     984                 :             : 
     985                 :           0 :     MemoryContextSwitchTo(oldcontext);
     986                 :             : 
     987                 :             :     /*
     988                 :             :      * Wait to validate the HBA against the registered options until later
     989                 :             :      * (see check_validator_hba_options()).
     990                 :             :      *
     991                 :             :      * Delaying allows the validator to make multiple registration calls, to
     992                 :             :      * append to the list; it lets us make the check in a place where we can
     993                 :             :      * report the error without leaking details to the client; and it avoids
     994                 :             :      * exporting the order of operations between HBA matching and the
     995                 :             :      * startup_cb call as an API guarantee. (The last issue may become
     996                 :             :      * relevant with a threaded model.)
     997                 :             :      */
     998                 :             : }
     999                 :             : 
    1000                 :             : /*
    1001                 :             :  * Restrict the names available to custom HBA options, so that we don't
    1002                 :             :  * accidentally prevent future syntax extensions to HBA files.
    1003                 :             :  */
    1004                 :             : bool
    1005                 :           0 : valid_oauth_hba_option_name(const char *name)
    1006                 :             : {
    1007                 :             :     /*
    1008                 :             :      * This list is not incredibly principled, since the goal is just to bound
    1009                 :             :      * compatibility guarantees for our HBA parser. Alphanumerics seem
    1010                 :             :      * obviously fine, and it's difficult to argue against the punctuation
    1011                 :             :      * that's already included in some HBA option names and identifiers.
    1012                 :             :      */
    1013                 :             :     static const char *name_allowed_set =
    1014                 :             :         "abcdefghijklmnopqrstuvwxyz"
    1015                 :             :         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    1016                 :             :         "0123456789_-";
    1017                 :             : 
    1018                 :             :     size_t      span;
    1019                 :             : 
    1020         [ #  # ]:           0 :     if (!name[0])
    1021                 :           0 :         return false;
    1022                 :             : 
    1023                 :           0 :     span = strspn(name, name_allowed_set);
    1024                 :           0 :     return name[span] == '\0';
    1025                 :             : }
    1026                 :             : 
    1027                 :             : /*
    1028                 :             :  * Verifies that all validator.* HBA options specified by the user were actually
    1029                 :             :  * registered by the validator library in use.
    1030                 :             :  */
    1031                 :             : static bool
    1032                 :           0 : check_validator_hba_options(Port *port, const char **logdetail)
    1033                 :             : {
    1034                 :           0 :     HbaLine    *hba = port->hba;
    1035                 :             : 
    1036   [ #  #  #  #  :           0 :     foreach_ptr(char, key, hba->oauth_opt_keys)
                   #  # ]
    1037                 :             :     {
    1038                 :           0 :         bool        found = false;
    1039                 :             : 
    1040                 :             :         /* O(n^2) shouldn't be a problem here in practice. */
    1041   [ #  #  #  #  :           0 :         foreach_ptr(char, optname, ValidatorOptions)
                   #  # ]
    1042                 :             :         {
    1043         [ #  # ]:           0 :             if (strcmp(key, optname) == 0)
    1044                 :             :             {
    1045                 :           0 :                 found = true;
    1046                 :           0 :                 break;
    1047                 :             :             }
    1048                 :             :         }
    1049                 :             : 
    1050         [ #  # ]:           0 :         if (!found)
    1051                 :             :         {
    1052                 :             :             /*
    1053                 :             :              * Unknown option name. Mirror the error messages in hba.c here,
    1054                 :             :              * keeping in mind that the original "validator." prefix was
    1055                 :             :              * stripped from the key during parsing.
    1056                 :             :              *
    1057                 :             :              * Since this is affecting live connections, which is unusual for
    1058                 :             :              * HBA, be noisy with a WARNING. (Warnings aren't sent to clients
    1059                 :             :              * prior to successful authentication, so this won't disclose the
    1060                 :             :              * server config.) It'll duplicate some of the information in the
    1061                 :             :              * logdetail, but that should make it hard to miss the connection
    1062                 :             :              * between the two.
    1063                 :             :              */
    1064                 :           0 :             char       *name = psprintf("validator.%s", key);
    1065                 :             : 
    1066                 :           0 :             *logdetail = psprintf(_("unrecognized authentication option name: \"%s\""),
    1067                 :             :                                   name);
    1068         [ #  # ]:           0 :             ereport(WARNING,
    1069                 :             :                     errcode(ERRCODE_CONFIG_FILE_ERROR),
    1070                 :             :                     errmsg("unrecognized authentication option name: \"%s\"",
    1071                 :             :                            name),
    1072                 :             :             /* translator: the first %s is the name of the module */
    1073                 :             :                     errdetail("The installed validator module (\"%s\") did not define an option named \"%s\".",
    1074                 :             :                               hba->oauth_validator, key),
    1075                 :             :                     errhint("All OAuth connections matching this line will fail. Correct the option and reload the server configuration."),
    1076                 :             :                     errcontext("line %d of configuration file \"%s\"",
    1077                 :             :                                hba->linenumber, hba->sourcefile));
    1078                 :             : 
    1079                 :           0 :             return false;
    1080                 :             :         }
    1081                 :             :     }
    1082                 :             : 
    1083                 :           0 :     ValidatorOptionsChecked = true; /* unfetter GetOAuthHBAOption() */
    1084                 :           0 :     return true;
    1085                 :             : }
    1086                 :             : 
    1087                 :             : /*
    1088                 :             :  * Retrieves the setting for a validator.* HBA option, or NULL if not found.
    1089                 :             :  * This may only be used during the validate_cb and shutdown_cb.
    1090                 :             :  */
    1091                 :             : const char *
    1092                 :           0 : GetOAuthHBAOption(const ValidatorModuleState *state, const char *optname)
    1093                 :             : {
    1094                 :           0 :     HbaLine    *hba = MyProcPort->hba;
    1095                 :             :     ListCell   *lc_k;
    1096                 :             :     ListCell   *lc_v;
    1097                 :           0 :     const char *ret = NULL;
    1098                 :             : 
    1099         [ #  # ]:           0 :     if (!ValidatorOptionsChecked)
    1100                 :             :     {
    1101                 :             :         /*
    1102                 :             :          * Prevent the startup_cb from retrieving HBA options that it has just
    1103                 :             :          * registered. This probably seems strange -- why refuse to hand out
    1104                 :             :          * information we already know? -- but this lets us reserve the
    1105                 :             :          * ability to perform the startup_cb call earlier, before we know
    1106                 :             :          * which HBA line is matched by a connection, without breaking this
    1107                 :             :          * API.
    1108                 :             :          */
    1109                 :           0 :         return NULL;
    1110                 :             :     }
    1111                 :             : 
    1112   [ #  #  #  # ]:           0 :     if (!state || !hba)
    1113                 :             :     {
    1114                 :             :         Assert(false);
    1115                 :           0 :         return NULL;
    1116                 :             :     }
    1117                 :             : 
    1118                 :             :     Assert(list_length(hba->oauth_opt_keys) == list_length(hba->oauth_opt_vals));
    1119                 :             : 
    1120   [ #  #  #  #  :           0 :     forboth(lc_k, hba->oauth_opt_keys, lc_v, hba->oauth_opt_vals)
          #  #  #  #  #  
             #  #  #  #  
                      # ]
    1121                 :             :     {
    1122                 :           0 :         const char *key = lfirst(lc_k);
    1123                 :           0 :         const char *val = lfirst(lc_v);
    1124                 :             : 
    1125         [ #  # ]:           0 :         if (strcmp(key, optname) == 0)
    1126                 :             :         {
    1127                 :             :             /*
    1128                 :             :              * Don't return yet -- when regular HBA options are specified more
    1129                 :             :              * than once, the last one wins. Do the same for these options.
    1130                 :             :              */
    1131                 :           0 :             ret = val;
    1132                 :             :         }
    1133                 :             :     }
    1134                 :             : 
    1135                 :           0 :     return ret;
    1136                 :             : }
        

Generated by: LCOV version 2.0-1