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