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-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
1522 michael@paquier.xyz 44 :CBC 163 : 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 : 163 : void *opaq = NULL;
51 : 163 : char *output = NULL;
52 : 163 : 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 : 163 : initStringInfo(&sasl_mechs);
63 : :
64 : 163 : mech->get_mechanisms(port, &sasl_mechs);
65 : : /* Put another '\0' to mark that list is finished. */
66 : 163 : appendStringInfoChar(&sasl_mechs, '\0');
67 : :
68 : 163 : sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
69 : 163 : 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 : 163 : initial = true;
78 : : do
79 : : {
80 : 281 : pq_startmsgread();
81 : 281 : mtype = pq_getbyte();
746 nathan@postgresql.or 82 [ + + ]: 281 : if (mtype != PqMsg_SASLResponse)
83 : : {
84 : : /* Only log error if client didn't disconnect. */
1522 michael@paquier.xyz 85 [ - + ]: 16 : if (mtype != EOF)
86 : : {
1522 michael@paquier.xyz 87 [ # # ]:UBC 0 : ereport(ERROR,
88 : : (errcode(ERRCODE_PROTOCOL_VIOLATION),
89 : : errmsg("expected SASL response, got message type %d",
90 : : mtype)));
91 : : }
92 : : else
1522 michael@paquier.xyz 93 :CBC 16 : return STATUS_EOF;
94 : : }
95 : :
96 : : /* Get the actual SASL message */
97 : 265 : initStringInfo(&buf);
318 dgustafsson@postgres 98 [ - + ]: 265 : if (pq_getmessage(&buf, mech->max_message_length))
99 : : {
100 : : /* EOF - pq_getmessage already logged error */
1522 michael@paquier.xyz 101 :UBC 0 : pfree(buf.data);
102 : 0 : return STATUS_ERROR;
103 : : }
104 : :
1522 michael@paquier.xyz 105 [ - + ]:CBC 265 : 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 [ + + ]: 265 : if (initial)
114 : : {
115 : : const char *selected_mech;
116 : :
117 : 147 : 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 : 147 : opaq = mech->init(port, selected_mech, shadow_pass);
132 : :
133 : 146 : inputlen = pq_getmsgint(&buf, 4);
134 [ - + ]: 146 : if (inputlen == -1)
1522 michael@paquier.xyz 135 :UBC 0 : input = NULL;
136 : : else
1522 michael@paquier.xyz 137 :CBC 146 : input = pq_getmsgbytes(&buf, inputlen);
138 : :
139 : 146 : initial = false;
140 : : }
141 : : else
142 : : {
143 : 118 : inputlen = buf.len;
144 : 118 : input = pq_getmsgbytes(&buf, buf.len);
145 : : }
146 : 264 : pq_getmsgend(&buf);
147 : :
148 : : /*
149 : : * The StringInfo guarantees that there's a \0 byte after the
150 : : * response.
151 : : */
152 [ + - - + ]: 264 : Assert(input == NULL || input[inputlen] == '\0');
153 : :
154 : : /*
155 : : * Hand the incoming message to the mechanism implementation.
156 : : */
157 : 264 : result = mech->exchange(opaq, input, inputlen,
158 : : &output, &outputlen,
159 : : logdetail);
160 : :
161 : : /* input buffer no longer used */
162 : 263 : pfree(buf.data);
163 : :
164 [ + + ]: 263 : 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 : : */
1519 170 [ - + ]: 171 : if (result == PG_SASL_EXCHANGE_FAILURE)
1519 michael@paquier.xyz 171 [ # # ]:UBC 0 : elog(ERROR, "output message found after SASL exchange failure");
172 : :
173 : : /*
174 : : * Negotiation generated data to be sent to the client.
175 : : */
1459 peter@eisentraut.org 176 [ - + ]:CBC 171 : elog(DEBUG4, "sending SASL challenge of length %d", outputlen);
177 : :
1522 michael@paquier.xyz 178 [ + + ]: 171 : if (result == PG_SASL_EXCHANGE_SUCCESS)
179 : 53 : sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
180 : : else
181 : 118 : sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
182 : :
183 : 171 : pfree(output);
184 : : }
185 [ + + ]: 263 : } while (result == PG_SASL_EXCHANGE_CONTINUE);
186 : :
187 : : /* Oops, Something bad happened */
188 [ + + ]: 145 : if (result != PG_SASL_EXCHANGE_SUCCESS)
189 : : {
190 : 65 : return STATUS_ERROR;
191 : : }
192 : :
193 : 80 : return STATUS_OK;
194 : : }
|