Age Owner Branch data TLA 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
1763 michael@paquier.xyz 50 :CBC 190 : 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 : 190 : void *opaq = NULL;
57 : 190 : char *output = NULL;
58 : 190 : 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 : 190 : initStringInfo(&sasl_mechs);
69 : :
70 : 190 : mech->get_mechanisms(port, &sasl_mechs);
71 : : /* Put another '\0' to mark that list is finished. */
72 : 190 : appendStringInfoChar(&sasl_mechs, '\0');
73 : :
74 : 190 : sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
75 : 190 : 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 : 190 : initial = true;
84 : : do
85 : : {
86 : 328 : pq_startmsgread();
87 : 328 : mtype = pq_getbyte();
987 nathan@postgresql.or 88 [ + + ]: 328 : if (mtype != PqMsg_SASLResponse)
89 : : {
90 : : /* Only log error if client didn't disconnect. */
1763 michael@paquier.xyz 91 [ - + ]: 16 : if (mtype != EOF)
92 : : {
1763 michael@paquier.xyz 93 [ # # ]:UBC 0 : ereport(ERROR,
94 : : (errcode(ERRCODE_PROTOCOL_VIOLATION),
95 : : errmsg("expected SASL response, got message type %d",
96 : : mtype)));
97 : : }
98 : : else
1763 michael@paquier.xyz 99 :CBC 16 : return STATUS_EOF;
100 : : }
101 : :
102 : : /* Get the actual SASL message */
103 : 312 : initStringInfo(&buf);
559 dgustafsson@postgres 104 [ - + ]: 312 : if (pq_getmessage(&buf, mech->max_message_length))
105 : : {
106 : : /* EOF - pq_getmessage already logged error */
1763 michael@paquier.xyz 107 :UBC 0 : pfree(buf.data);
108 : 0 : return STATUS_ERROR;
109 : : }
110 : :
1763 michael@paquier.xyz 111 [ - + ]:CBC 312 : 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 [ + + ]: 312 : if (initial)
120 : : {
121 : : const char *selected_mech;
122 : :
123 : 174 : 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 : 174 : opaq = mech->init(port, selected_mech, shadow_pass);
138 : :
139 : 173 : inputlen = pq_getmsgint(&buf, 4);
140 [ - + ]: 173 : if (inputlen == -1)
1763 michael@paquier.xyz 141 :UBC 0 : input = NULL;
142 : : else
1763 michael@paquier.xyz 143 :CBC 173 : input = pq_getmsgbytes(&buf, inputlen);
144 : :
145 : 173 : initial = false;
146 : : }
147 : : else
148 : : {
149 : 138 : inputlen = buf.len;
150 : 138 : input = pq_getmsgbytes(&buf, buf.len);
151 : : }
152 : 311 : pq_getmsgend(&buf);
153 : :
154 : : /*
155 : : * The StringInfo guarantees that there's a \0 byte after the
156 : : * response.
157 : : */
158 [ + - - + ]: 311 : Assert(input == NULL || input[inputlen] == '\0');
159 : :
160 : : /*
161 : : * Hand the incoming message to the mechanism implementation.
162 : : */
163 : 311 : result = mech->exchange(opaq, input, inputlen,
164 : : &output, &outputlen,
165 : : logdetail);
166 : :
167 : : /* input buffer no longer used */
168 : 310 : pfree(buf.data);
169 : :
170 [ + + ]: 310 : 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 : : */
35 jchampion@postgresql 176 [ + - - + ]:GNC 193 : if (result == PG_SASL_EXCHANGE_FAILURE || result == PG_SASL_EXCHANGE_ABANDONED)
1760 michael@paquier.xyz 177 [ # # ]:UBC 0 : elog(ERROR, "output message found after SASL exchange failure");
178 : :
179 : : /*
180 : : * Negotiation generated data to be sent to the client.
181 : : */
1700 peter@eisentraut.org 182 [ - + ]:CBC 193 : elog(DEBUG4, "sending SASL challenge of length %d", outputlen);
183 : :
1763 michael@paquier.xyz 184 [ + + ]: 193 : if (result == PG_SASL_EXCHANGE_SUCCESS)
185 : 55 : sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
186 : : else
187 : 138 : sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
188 : :
189 : 193 : pfree(output);
190 : : }
191 [ + + ]: 310 : } while (result == PG_SASL_EXCHANGE_CONTINUE);
192 : :
35 jchampion@postgresql 193 [ + + ]:GNC 172 : if (result == PG_SASL_EXCHANGE_ABANDONED)
194 : : {
195 [ - + ]: 69 : if (!abandoned)
196 : : {
197 : : /*
198 : : * Programmer error: caller needs to track the abandoned state for
199 : : * this mechanism.
200 : : */
35 jchampion@postgresql 201 [ # # ]:UNC 0 : elog(ERROR, "SASL exchange was abandoned, but CheckSASLAuth isn't tracking it");
202 : : }
203 : :
35 jchampion@postgresql 204 :GNC 69 : *abandoned = true;
205 : : }
206 : :
207 : : /* Oops, Something bad happened */
1763 michael@paquier.xyz 208 [ + + ]:CBC 172 : if (result != PG_SASL_EXCHANGE_SUCCESS)
209 : : {
210 : 84 : return STATUS_ERROR;
211 : : }
212 : :
213 : 88 : return STATUS_OK;
214 : : }
|