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-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
1012 michael@paquier.xyz 52 :CBC 51 : CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
53 : : const char **logdetail)
54 : : {
55 : : StringInfoData sasl_mechs;
56 : : int mtype;
57 : : StringInfoData buf;
58 : 51 : void *opaq = NULL;
59 : 51 : char *output = NULL;
60 : 51 : 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 : 51 : initStringInfo(&sasl_mechs);
71 : :
72 : 51 : mech->get_mechanisms(port, &sasl_mechs);
73 : : /* Put another '\0' to mark that list is finished. */
74 : 51 : appendStringInfoChar(&sasl_mechs, '\0');
75 : :
76 : 51 : sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
77 : 51 : 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 : 51 : initial = true;
86 : : do
87 : : {
88 : 91 : pq_startmsgread();
89 : 91 : mtype = pq_getbyte();
236 nathan@postgresql.or 90 [ + + ]:GNC 91 : if (mtype != PqMsg_SASLResponse)
91 : : {
92 : : /* Only log error if client didn't disconnect. */
1012 michael@paquier.xyz 93 [ - + ]:CBC 11 : if (mtype != EOF)
94 : : {
1012 michael@paquier.xyz 95 [ # # ]:UBC 0 : ereport(ERROR,
96 : : (errcode(ERRCODE_PROTOCOL_VIOLATION),
97 : : errmsg("expected SASL response, got message type %d",
98 : : mtype)));
99 : : }
100 : : else
1012 michael@paquier.xyz 101 :CBC 11 : return STATUS_EOF;
102 : : }
103 : :
104 : : /* Get the actual SASL message */
105 : 80 : initStringInfo(&buf);
106 [ - + ]: 80 : if (pq_getmessage(&buf, PG_MAX_SASL_MESSAGE_LENGTH))
107 : : {
108 : : /* EOF - pq_getmessage already logged error */
1012 michael@paquier.xyz 109 :UBC 0 : pfree(buf.data);
110 : 0 : return STATUS_ERROR;
111 : : }
112 : :
1012 michael@paquier.xyz 113 [ - + ]:CBC 80 : 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 [ + + ]: 80 : if (initial)
122 : : {
123 : : const char *selected_mech;
124 : :
125 : 40 : 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 : 40 : opaq = mech->init(port, selected_mech, shadow_pass);
140 : :
141 : 40 : inputlen = pq_getmsgint(&buf, 4);
142 [ - + ]: 40 : if (inputlen == -1)
1012 michael@paquier.xyz 143 :UBC 0 : input = NULL;
144 : : else
1012 michael@paquier.xyz 145 :CBC 40 : input = pq_getmsgbytes(&buf, inputlen);
146 : :
147 : 40 : initial = false;
148 : : }
149 : : else
150 : : {
151 : 40 : inputlen = buf.len;
152 : 40 : input = pq_getmsgbytes(&buf, buf.len);
153 : : }
154 : 80 : pq_getmsgend(&buf);
155 : :
156 : : /*
157 : : * The StringInfo guarantees that there's a \0 byte after the
158 : : * response.
159 : : */
160 [ + - - + ]: 80 : Assert(input == NULL || input[inputlen] == '\0');
161 : :
162 : : /*
163 : : * Hand the incoming message to the mechanism implementation.
164 : : */
165 : 80 : result = mech->exchange(opaq, input, inputlen,
166 : : &output, &outputlen,
167 : : logdetail);
168 : :
169 : : /* input buffer no longer used */
170 : 80 : pfree(buf.data);
171 : :
172 [ + + ]: 80 : 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 : : */
1009 178 [ - + ]: 76 : if (result == PG_SASL_EXCHANGE_FAILURE)
1009 michael@paquier.xyz 179 [ # # ]:UBC 0 : elog(ERROR, "output message found after SASL exchange failure");
180 : :
181 : : /*
182 : : * Negotiation generated data to be sent to the client.
183 : : */
949 peter@eisentraut.org 184 [ - + ]:CBC 76 : elog(DEBUG4, "sending SASL challenge of length %d", outputlen);
185 : :
1012 michael@paquier.xyz 186 [ + + ]: 76 : if (result == PG_SASL_EXCHANGE_SUCCESS)
187 : 36 : sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
188 : : else
189 : 40 : sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
190 : :
191 : 76 : pfree(output);
192 : : }
193 [ + + ]: 80 : } while (result == PG_SASL_EXCHANGE_CONTINUE);
194 : :
195 : : /* Oops, Something bad happened */
196 [ + + ]: 40 : if (result != PG_SASL_EXCHANGE_SUCCESS)
197 : : {
198 : 4 : return STATUS_ERROR;
199 : : }
200 : :
201 : 36 : return STATUS_OK;
202 : : }
|