Coverage Report

Created: 2025-06-13 06:06

/src/postgres/src/backend/libpq/auth-sasl.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * auth-sasl.c
4
 *    Routines to handle authentication via SASL
5
 *
6
 * Portions Copyright (c) 1996-2025, 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
CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
45
        const char **logdetail)
46
0
{
47
0
  StringInfoData sasl_mechs;
48
0
  int     mtype;
49
0
  StringInfoData buf;
50
0
  void     *opaq = NULL;
51
0
  char     *output = NULL;
52
0
  int     outputlen = 0;
53
0
  const char *input;
54
0
  int     inputlen;
55
0
  int     result;
56
0
  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
0
  initStringInfo(&sasl_mechs);
63
64
0
  mech->get_mechanisms(port, &sasl_mechs);
65
  /* Put another '\0' to mark that list is finished. */
66
0
  appendStringInfoChar(&sasl_mechs, '\0');
67
68
0
  sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
69
0
  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
0
  initial = true;
78
0
  do
79
0
  {
80
0
    pq_startmsgread();
81
0
    mtype = pq_getbyte();
82
0
    if (mtype != PqMsg_SASLResponse)
83
0
    {
84
      /* Only log error if client didn't disconnect. */
85
0
      if (mtype != EOF)
86
0
      {
87
0
        ereport(ERROR,
88
0
            (errcode(ERRCODE_PROTOCOL_VIOLATION),
89
0
             errmsg("expected SASL response, got message type %d",
90
0
                mtype)));
91
0
      }
92
0
      else
93
0
        return STATUS_EOF;
94
0
    }
95
96
    /* Get the actual SASL message */
97
0
    initStringInfo(&buf);
98
0
    if (pq_getmessage(&buf, mech->max_message_length))
99
0
    {
100
      /* EOF - pq_getmessage already logged error */
101
0
      pfree(buf.data);
102
0
      return STATUS_ERROR;
103
0
    }
104
105
0
    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
0
    if (initial)
114
0
    {
115
0
      const char *selected_mech;
116
117
0
      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
0
      opaq = mech->init(port, selected_mech, shadow_pass);
132
133
0
      inputlen = pq_getmsgint(&buf, 4);
134
0
      if (inputlen == -1)
135
0
        input = NULL;
136
0
      else
137
0
        input = pq_getmsgbytes(&buf, inputlen);
138
139
0
      initial = false;
140
0
    }
141
0
    else
142
0
    {
143
0
      inputlen = buf.len;
144
0
      input = pq_getmsgbytes(&buf, buf.len);
145
0
    }
146
0
    pq_getmsgend(&buf);
147
148
    /*
149
     * The StringInfo guarantees that there's a \0 byte after the
150
     * response.
151
     */
152
0
    Assert(input == NULL || input[inputlen] == '\0');
153
154
    /*
155
     * Hand the incoming message to the mechanism implementation.
156
     */
157
0
    result = mech->exchange(opaq, input, inputlen,
158
0
                &output, &outputlen,
159
0
                logdetail);
160
161
    /* input buffer no longer used */
162
0
    pfree(buf.data);
163
164
0
    if (output)
165
0
    {
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
0
      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
0
      elog(DEBUG4, "sending SASL challenge of length %d", outputlen);
177
178
0
      if (result == PG_SASL_EXCHANGE_SUCCESS)
179
0
        sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
180
0
      else
181
0
        sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
182
183
0
      pfree(output);
184
0
    }
185
0
  } while (result == PG_SASL_EXCHANGE_CONTINUE);
186
187
  /* Oops, Something bad happened */
188
0
  if (result != PG_SASL_EXCHANGE_SUCCESS)
189
0
  {
190
0
    return STATUS_ERROR;
191
0
  }
192
193
0
  return STATUS_OK;
194
0
}