LCOV - code coverage report
Current view: top level - src/backend/libpq - auth-sasl.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 89.1 % 46 41
Test Date: 2026-02-28 15:14:49 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              :  * Mechanisms must take care not to reveal to the client that a user entry
      34              :  * does not exist; ideally, the external failure mode is identical to that
      35              :  * of an incorrect password.  Mechanisms may instead use the logdetail
      36              :  * output parameter to internally differentiate between failure cases and
      37              :  * assist debugging by the server admin.
      38              :  *
      39              :  * A mechanism is not required to utilize a shadow entry, or even a password
      40              :  * system at all; for these cases, shadow_pass may be ignored and the caller
      41              :  * should just pass NULL.
      42              :  */
      43              : int
      44           69 : CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
      45              :               const char **logdetail)
      46              : {
      47              :     StringInfoData sasl_mechs;
      48              :     int         mtype;
      49              :     StringInfoData buf;
      50           69 :     void       *opaq = NULL;
      51           69 :     char       *output = NULL;
      52           69 :     int         outputlen = 0;
      53              :     const char *input;
      54              :     int         inputlen;
      55              :     int         result;
      56              :     bool        initial;
      57              : 
      58              :     /*
      59              :      * Send the SASL authentication request to user.  It includes the list of
      60              :      * authentication mechanisms that are supported.
      61              :      */
      62           69 :     initStringInfo(&sasl_mechs);
      63              : 
      64           69 :     mech->get_mechanisms(port, &sasl_mechs);
      65              :     /* Put another '\0' to mark that list is finished. */
      66           69 :     appendStringInfoChar(&sasl_mechs, '\0');
      67              : 
      68           69 :     sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
      69           69 :     pfree(sasl_mechs.data);
      70              : 
      71              :     /*
      72              :      * Loop through SASL message exchange.  This exchange can consist of
      73              :      * multiple messages sent in both directions.  First message is always
      74              :      * from the client.  All messages from client to server are password
      75              :      * packets (type 'p').
      76              :      */
      77           69 :     initial = true;
      78              :     do
      79              :     {
      80          127 :         pq_startmsgread();
      81          127 :         mtype = pq_getbyte();
      82          127 :         if (mtype != PqMsg_SASLResponse)
      83              :         {
      84              :             /* Only log error if client didn't disconnect. */
      85           11 :             if (mtype != EOF)
      86              :             {
      87            0 :                 ereport(ERROR,
      88              :                         (errcode(ERRCODE_PROTOCOL_VIOLATION),
      89              :                          errmsg("expected SASL response, got message type %d",
      90              :                                 mtype)));
      91              :             }
      92              :             else
      93           11 :                 return STATUS_EOF;
      94              :         }
      95              : 
      96              :         /* Get the actual SASL message */
      97          116 :         initStringInfo(&buf);
      98          116 :         if (pq_getmessage(&buf, mech->max_message_length))
      99              :         {
     100              :             /* EOF - pq_getmessage already logged error */
     101            0 :             pfree(buf.data);
     102            0 :             return STATUS_ERROR;
     103              :         }
     104              : 
     105          116 :         elog(DEBUG4, "processing received SASL response of length %d", buf.len);
     106              : 
     107              :         /*
     108              :          * The first SASLInitialResponse message is different from the others.
     109              :          * It indicates which SASL mechanism the client selected, and contains
     110              :          * an optional Initial Client Response payload.  The subsequent
     111              :          * SASLResponse messages contain just the SASL payload.
     112              :          */
     113          116 :         if (initial)
     114              :         {
     115              :             const char *selected_mech;
     116              : 
     117           58 :             selected_mech = pq_getmsgrawstring(&buf);
     118              : 
     119              :             /*
     120              :              * Initialize the status tracker for message exchanges.
     121              :              *
     122              :              * If the user doesn't exist, or doesn't have a valid password, or
     123              :              * it's expired, we still go through the motions of SASL
     124              :              * authentication, but tell the authentication method that the
     125              :              * authentication is "doomed". That is, it's going to fail, no
     126              :              * matter what.
     127              :              *
     128              :              * This is because we don't want to reveal to an attacker what
     129              :              * usernames are valid, nor which users have a valid password.
     130              :              */
     131           58 :             opaq = mech->init(port, selected_mech, shadow_pass);
     132              : 
     133           58 :             inputlen = pq_getmsgint(&buf, 4);
     134           58 :             if (inputlen == -1)
     135            0 :                 input = NULL;
     136              :             else
     137           58 :                 input = pq_getmsgbytes(&buf, inputlen);
     138              : 
     139           58 :             initial = false;
     140              :         }
     141              :         else
     142              :         {
     143           58 :             inputlen = buf.len;
     144           58 :             input = pq_getmsgbytes(&buf, buf.len);
     145              :         }
     146          116 :         pq_getmsgend(&buf);
     147              : 
     148              :         /*
     149              :          * The StringInfo guarantees that there's a \0 byte after the
     150              :          * response.
     151              :          */
     152              :         Assert(input == NULL || input[inputlen] == '\0');
     153              : 
     154              :         /*
     155              :          * Hand the incoming message to the mechanism implementation.
     156              :          */
     157          116 :         result = mech->exchange(opaq, input, inputlen,
     158              :                                 &output, &outputlen,
     159              :                                 logdetail);
     160              : 
     161              :         /* input buffer no longer used */
     162          116 :         pfree(buf.data);
     163              : 
     164          116 :         if (output)
     165              :         {
     166              :             /*
     167              :              * PG_SASL_EXCHANGE_FAILURE with some output is forbidden by SASL.
     168              :              * Make sure here that the mechanism used got that right.
     169              :              */
     170          110 :             if (result == PG_SASL_EXCHANGE_FAILURE)
     171            0 :                 elog(ERROR, "output message found after SASL exchange failure");
     172              : 
     173              :             /*
     174              :              * Negotiation generated data to be sent to the client.
     175              :              */
     176          110 :             elog(DEBUG4, "sending SASL challenge of length %d", outputlen);
     177              : 
     178          110 :             if (result == PG_SASL_EXCHANGE_SUCCESS)
     179           52 :                 sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
     180              :             else
     181           58 :                 sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
     182              : 
     183          110 :             pfree(output);
     184              :         }
     185          116 :     } while (result == PG_SASL_EXCHANGE_CONTINUE);
     186              : 
     187              :     /* Oops, Something bad happened */
     188           58 :     if (result != PG_SASL_EXCHANGE_SUCCESS)
     189              :     {
     190            6 :         return STATUS_ERROR;
     191              :     }
     192              : 
     193           52 :     return STATUS_OK;
     194              : }
        

Generated by: LCOV version 2.0-1