PostgreSQL Source Code  git master
auth-sasl.c
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2  *
3  * auth-sasl.c
4  * Routines to handle authentication via SASL
5  *
6  * Portions Copyright (c) 1996-2024, 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  * Maximum accepted size of SASL messages.
25  *
26  * The messages that the server or libpq generate are much smaller than this,
27  * but have some headroom.
28  */
29 #define PG_MAX_SASL_MESSAGE_LENGTH 1024
30 
31 /*
32  * Perform a SASL exchange with a libpq client, using a specific mechanism
33  * implementation.
34  *
35  * shadow_pass is an optional pointer to the stored secret of the role
36  * authenticated, from pg_authid.rolpassword. For mechanisms that use
37  * shadowed passwords, a NULL pointer here means that an entry could not
38  * be found for the role (or the user does not exist), and the mechanism
39  * should fail the authentication exchange.
40  *
41  * Mechanisms must take care not to reveal to the client that a user entry
42  * does not exist; ideally, the external failure mode is identical to that
43  * of an incorrect password. Mechanisms may instead use the logdetail
44  * output parameter to internally differentiate between failure cases and
45  * assist debugging by the server admin.
46  *
47  * A mechanism is not required to utilize a shadow entry, or even a password
48  * system at all; for these cases, shadow_pass may be ignored and the caller
49  * should just pass NULL.
50  */
51 int
52 CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
53  const char **logdetail)
54 {
55  StringInfoData sasl_mechs;
56  int mtype;
58  void *opaq = NULL;
59  char *output = NULL;
60  int outputlen = 0;
61  const char *input;
62  int inputlen;
63  int result;
64  bool initial;
65 
66  /*
67  * Send the SASL authentication request to user. It includes the list of
68  * authentication mechanisms that are supported.
69  */
70  initStringInfo(&sasl_mechs);
71 
72  mech->get_mechanisms(port, &sasl_mechs);
73  /* Put another '\0' to mark that list is finished. */
74  appendStringInfoChar(&sasl_mechs, '\0');
75 
76  sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
77  pfree(sasl_mechs.data);
78 
79  /*
80  * Loop through SASL message exchange. This exchange can consist of
81  * multiple messages sent in both directions. First message is always
82  * from the client. All messages from client to server are password
83  * packets (type 'p').
84  */
85  initial = true;
86  do
87  {
89  mtype = pq_getbyte();
90  if (mtype != PqMsg_SASLResponse)
91  {
92  /* Only log error if client didn't disconnect. */
93  if (mtype != EOF)
94  {
95  ereport(ERROR,
96  (errcode(ERRCODE_PROTOCOL_VIOLATION),
97  errmsg("expected SASL response, got message type %d",
98  mtype)));
99  }
100  else
101  return STATUS_EOF;
102  }
103 
104  /* Get the actual SASL message */
107  {
108  /* EOF - pq_getmessage already logged error */
109  pfree(buf.data);
110  return STATUS_ERROR;
111  }
112 
113  elog(DEBUG4, "processing received SASL response of length %d", buf.len);
114 
115  /*
116  * The first SASLInitialResponse message is different from the others.
117  * It indicates which SASL mechanism the client selected, and contains
118  * an optional Initial Client Response payload. The subsequent
119  * SASLResponse messages contain just the SASL payload.
120  */
121  if (initial)
122  {
123  const char *selected_mech;
124 
125  selected_mech = pq_getmsgrawstring(&buf);
126 
127  /*
128  * Initialize the status tracker for message exchanges.
129  *
130  * If the user doesn't exist, or doesn't have a valid password, or
131  * it's expired, we still go through the motions of SASL
132  * authentication, but tell the authentication method that the
133  * authentication is "doomed". That is, it's going to fail, no
134  * matter what.
135  *
136  * This is because we don't want to reveal to an attacker what
137  * usernames are valid, nor which users have a valid password.
138  */
139  opaq = mech->init(port, selected_mech, shadow_pass);
140 
141  inputlen = pq_getmsgint(&buf, 4);
142  if (inputlen == -1)
143  input = NULL;
144  else
145  input = pq_getmsgbytes(&buf, inputlen);
146 
147  initial = false;
148  }
149  else
150  {
151  inputlen = buf.len;
152  input = pq_getmsgbytes(&buf, buf.len);
153  }
154  pq_getmsgend(&buf);
155 
156  /*
157  * The StringInfo guarantees that there's a \0 byte after the
158  * response.
159  */
160  Assert(input == NULL || input[inputlen] == '\0');
161 
162  /*
163  * Hand the incoming message to the mechanism implementation.
164  */
165  result = mech->exchange(opaq, input, inputlen,
166  &output, &outputlen,
167  logdetail);
168 
169  /* input buffer no longer used */
170  pfree(buf.data);
171 
172  if (output)
173  {
174  /*
175  * PG_SASL_EXCHANGE_FAILURE with some output is forbidden by SASL.
176  * Make sure here that the mechanism used got that right.
177  */
178  if (result == PG_SASL_EXCHANGE_FAILURE)
179  elog(ERROR, "output message found after SASL exchange failure");
180 
181  /*
182  * Negotiation generated data to be sent to the client.
183  */
184  elog(DEBUG4, "sending SASL challenge of length %d", outputlen);
185 
186  if (result == PG_SASL_EXCHANGE_SUCCESS)
188  else
190 
191  pfree(output);
192  }
193  } while (result == PG_SASL_EXCHANGE_CONTINUE);
194 
195  /* Oops, Something bad happened */
196  if (result != PG_SASL_EXCHANGE_SUCCESS)
197  {
198  return STATUS_ERROR;
199  }
200 
201  return STATUS_OK;
202 }
#define PG_MAX_SASL_MESSAGE_LENGTH
Definition: auth-sasl.c:29
int CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass, const char **logdetail)
Definition: auth-sasl.c:52
void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata, int extralen)
Definition: auth.c:676
#define STATUS_OK
Definition: c.h:1169
#define Assert(condition)
Definition: c.h:858
#define STATUS_EOF
Definition: c.h:1171
#define STATUS_ERROR
Definition: c.h:1170
int errcode(int sqlerrcode)
Definition: elog.c:859
int errmsg(const char *fmt,...)
Definition: elog.c:1072
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:224
#define ereport(elevel,...)
Definition: elog.h:149
#define DEBUG4
Definition: elog.h:27
FILE * input
FILE * output
void pfree(void *pointer)
Definition: mcxt.c:1520
static int port
Definition: pg_regress.c:116
static char * buf
Definition: pg_test_fsync.c:73
int pq_getmessage(StringInfo s, int maxlen)
Definition: pqcomm.c:1202
int pq_getbyte(void)
Definition: pqcomm.c:963
void pq_startmsgread(void)
Definition: pqcomm.c:1140
unsigned int pq_getmsgint(StringInfo msg, int b)
Definition: pqformat.c:415
void pq_getmsgend(StringInfo msg)
Definition: pqformat.c:635
const char * pq_getmsgbytes(StringInfo msg, int datalen)
Definition: pqformat.c:508
const char * pq_getmsgrawstring(StringInfo msg)
Definition: pqformat.c:608
#define AUTH_REQ_SASL_CONT
Definition: protocol.h:81
#define PqMsg_SASLResponse
Definition: protocol.h:33
#define AUTH_REQ_SASL
Definition: protocol.h:80
#define AUTH_REQ_SASL_FIN
Definition: protocol.h:82
#define PG_SASL_EXCHANGE_FAILURE
Definition: sasl.h:27
#define PG_SASL_EXCHANGE_CONTINUE
Definition: sasl.h:25
#define PG_SASL_EXCHANGE_SUCCESS
Definition: sasl.h:26
void appendStringInfoChar(StringInfo str, char ch)
Definition: stringinfo.c:194
void initStringInfo(StringInfo str)
Definition: stringinfo.c:59
Definition: libpq-be.h:133
int(* exchange)(void *state, const char *input, int inputlen, char **output, int *outputlen, const char **logdetail)
Definition: sasl.h:126
void(* get_mechanisms)(Port *port, StringInfo buf)
Definition: sasl.h:56
void *(* init)(Port *port, const char *mech, const char *shadow_pass)
Definition: sasl.h:80