LCOV - code coverage report
Current view: top level - src/backend/libpq - auth-sasl.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 84.0 % 50 42
Test Date: 2026-04-07 14:16:30 Functions: 100.0 % 1 1
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * auth-sasl.c
       4              :  *    Routines to handle authentication via SASL
       5              :  *
       6              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7              :  * Portions Copyright (c) 1994, Regents of the University of California
       8              :  *
       9              :  *
      10              :  * IDENTIFICATION
      11              :  *    src/backend/libpq/auth-sasl.c
      12              :  *
      13              :  *-------------------------------------------------------------------------
      14              :  */
      15              : 
      16              : #include "postgres.h"
      17              : 
      18              : #include "libpq/auth.h"
      19              : #include "libpq/libpq.h"
      20              : #include "libpq/pqformat.h"
      21              : #include "libpq/sasl.h"
      22              : 
      23              : /*
      24              :  * Perform a SASL exchange with a libpq client, using a specific mechanism
      25              :  * implementation.
      26              :  *
      27              :  * shadow_pass is an optional pointer to the stored secret of the role
      28              :  * authenticated, from pg_authid.rolpassword.  For mechanisms that use
      29              :  * shadowed passwords, a NULL pointer here means that an entry could not
      30              :  * be found for the role (or the user does not exist), and the mechanism
      31              :  * should fail the authentication exchange.
      32              :  *
      33              :  * Some SASL mechanisms (e.g. OAUTHBEARER) define special exchanges for
      34              :  * parameter discovery. These exchanges will always result in STATUS_ERROR,
      35              :  * since we can't let the connection continue, but we shouldn't consider them to
      36              :  * be failed authentication attempts. *abandoned will be set to true in this
      37              :  * case.
      38              :  *
      39              :  * Mechanisms must take care not to reveal to the client that a user entry
      40              :  * does not exist; ideally, the external failure mode is identical to that
      41              :  * of an incorrect password.  Mechanisms may instead use the logdetail
      42              :  * output parameter to internally differentiate between failure cases and
      43              :  * assist debugging by the server admin.
      44              :  *
      45              :  * A mechanism is not required to utilize a shadow entry, or even a password
      46              :  * system at all; for these cases, shadow_pass may be ignored and the caller
      47              :  * should just pass NULL.
      48              :  */
      49              : int
      50           72 : CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
      51              :               const char **logdetail, bool *abandoned)
      52              : {
      53              :     StringInfoData sasl_mechs;
      54              :     int         mtype;
      55              :     StringInfoData buf;
      56           72 :     void       *opaq = NULL;
      57           72 :     char       *output = NULL;
      58           72 :     int         outputlen = 0;
      59              :     const char *input;
      60              :     int         inputlen;
      61              :     int         result;
      62              :     bool        initial;
      63              : 
      64              :     /*
      65              :      * Send the SASL authentication request to user.  It includes the list of
      66              :      * authentication mechanisms that are supported.
      67              :      */
      68           72 :     initStringInfo(&sasl_mechs);
      69              : 
      70           72 :     mech->get_mechanisms(port, &sasl_mechs);
      71              :     /* Put another '\0' to mark that list is finished. */
      72           72 :     appendStringInfoChar(&sasl_mechs, '\0');
      73              : 
      74           72 :     sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
      75           72 :     pfree(sasl_mechs.data);
      76              : 
      77              :     /*
      78              :      * Loop through SASL message exchange.  This exchange can consist of
      79              :      * multiple messages sent in both directions.  First message is always
      80              :      * from the client.  All messages from client to server are password
      81              :      * packets (type 'p').
      82              :      */
      83           72 :     initial = true;
      84              :     do
      85              :     {
      86          133 :         pq_startmsgread();
      87          133 :         mtype = pq_getbyte();
      88          133 :         if (mtype != PqMsg_SASLResponse)
      89              :         {
      90              :             /* Only log error if client didn't disconnect. */
      91           11 :             if (mtype != EOF)
      92              :             {
      93            0 :                 ereport(ERROR,
      94              :                         (errcode(ERRCODE_PROTOCOL_VIOLATION),
      95              :                          errmsg("expected SASL response, got message type %d",
      96              :                                 mtype)));
      97              :             }
      98              :             else
      99           11 :                 return STATUS_EOF;
     100              :         }
     101              : 
     102              :         /* Get the actual SASL message */
     103          122 :         initStringInfo(&buf);
     104          122 :         if (pq_getmessage(&buf, mech->max_message_length))
     105              :         {
     106              :             /* EOF - pq_getmessage already logged error */
     107            0 :             pfree(buf.data);
     108            0 :             return STATUS_ERROR;
     109              :         }
     110              : 
     111          122 :         elog(DEBUG4, "processing received SASL response of length %d", buf.len);
     112              : 
     113              :         /*
     114              :          * The first SASLInitialResponse message is different from the others.
     115              :          * It indicates which SASL mechanism the client selected, and contains
     116              :          * an optional Initial Client Response payload.  The subsequent
     117              :          * SASLResponse messages contain just the SASL payload.
     118              :          */
     119          122 :         if (initial)
     120              :         {
     121              :             const char *selected_mech;
     122              : 
     123           61 :             selected_mech = pq_getmsgrawstring(&buf);
     124              : 
     125              :             /*
     126              :              * Initialize the status tracker for message exchanges.
     127              :              *
     128              :              * If the user doesn't exist, or doesn't have a valid password, or
     129              :              * it's expired, we still go through the motions of SASL
     130              :              * authentication, but tell the authentication method that the
     131              :              * authentication is "doomed". That is, it's going to fail, no
     132              :              * matter what.
     133              :              *
     134              :              * This is because we don't want to reveal to an attacker what
     135              :              * usernames are valid, nor which users have a valid password.
     136              :              */
     137           61 :             opaq = mech->init(port, selected_mech, shadow_pass);
     138              : 
     139           61 :             inputlen = pq_getmsgint(&buf, 4);
     140           61 :             if (inputlen == -1)
     141            0 :                 input = NULL;
     142              :             else
     143           61 :                 input = pq_getmsgbytes(&buf, inputlen);
     144              : 
     145           61 :             initial = false;
     146              :         }
     147              :         else
     148              :         {
     149           61 :             inputlen = buf.len;
     150           61 :             input = pq_getmsgbytes(&buf, buf.len);
     151              :         }
     152          122 :         pq_getmsgend(&buf);
     153              : 
     154              :         /*
     155              :          * The StringInfo guarantees that there's a \0 byte after the
     156              :          * response.
     157              :          */
     158              :         Assert(input == NULL || input[inputlen] == '\0');
     159              : 
     160              :         /*
     161              :          * Hand the incoming message to the mechanism implementation.
     162              :          */
     163          122 :         result = mech->exchange(opaq, input, inputlen,
     164              :                                 &output, &outputlen,
     165              :                                 logdetail);
     166              : 
     167              :         /* input buffer no longer used */
     168          122 :         pfree(buf.data);
     169              : 
     170          122 :         if (output)
     171              :         {
     172              :             /*
     173              :              * PG_SASL_EXCHANGE_FAILURE with some output is forbidden by SASL.
     174              :              * Make sure here that the mechanism used got that right.
     175              :              */
     176          116 :             if (result == PG_SASL_EXCHANGE_FAILURE || result == PG_SASL_EXCHANGE_ABANDONED)
     177            0 :                 elog(ERROR, "output message found after SASL exchange failure");
     178              : 
     179              :             /*
     180              :              * Negotiation generated data to be sent to the client.
     181              :              */
     182          116 :             elog(DEBUG4, "sending SASL challenge of length %d", outputlen);
     183              : 
     184          116 :             if (result == PG_SASL_EXCHANGE_SUCCESS)
     185           55 :                 sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
     186              :             else
     187           61 :                 sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
     188              : 
     189          116 :             pfree(output);
     190              :         }
     191          122 :     } while (result == PG_SASL_EXCHANGE_CONTINUE);
     192              : 
     193           61 :     if (result == PG_SASL_EXCHANGE_ABANDONED)
     194              :     {
     195            0 :         if (!abandoned)
     196              :         {
     197              :             /*
     198              :              * Programmer error: caller needs to track the abandoned state for
     199              :              * this mechanism.
     200              :              */
     201            0 :             elog(ERROR, "SASL exchange was abandoned, but CheckSASLAuth isn't tracking it");
     202              :         }
     203              : 
     204            0 :         *abandoned = true;
     205              :     }
     206              : 
     207              :     /* Oops, Something bad happened */
     208           61 :     if (result != PG_SASL_EXCHANGE_SUCCESS)
     209              :     {
     210            6 :         return STATUS_ERROR;
     211              :     }
     212              : 
     213           55 :     return STATUS_OK;
     214              : }
        

Generated by: LCOV version 2.0-1