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