qail_pg/driver/connection/
startup.rs1use super::helpers::{generate_gss_token, md5_password_message, select_scram_mechanism};
4use super::types::{GSS_SESSION_COUNTER, PgConnection, StartupAuthFlow};
5use crate::driver::stream::PgStream;
6use crate::driver::{AuthSettings, EnterpriseAuthMechanism, GssTokenProvider, PgError, PgResult};
7use crate::protocol::{BackendMessage, FrontendMessage, ScramClient, TransactionStatus};
8use sha2::{Digest, Sha256};
9use std::sync::atomic::Ordering;
10
11impl PgConnection {
12 pub(super) async fn handle_startup(
14 &mut self,
15 user: &str,
16 password: Option<&str>,
17 auth_settings: AuthSettings,
18 gss_token_provider: Option<GssTokenProvider>,
19 ) -> PgResult<()> {
20 let mut scram_client: Option<ScramClient> = None;
21 let mut startup_auth_flow: Option<StartupAuthFlow> = None;
22 let mut saw_auth_ok = false;
23 let gss_session_id = GSS_SESSION_COUNTER.fetch_add(1, Ordering::Relaxed);
24 let mut gss_roundtrips: u32 = 0;
25 const MAX_GSS_ROUNDTRIPS: u32 = 32;
26
27 loop {
28 let msg = self.recv().await?;
29 if saw_auth_ok
30 && matches!(
31 &msg,
32 BackendMessage::AuthenticationOk
33 | BackendMessage::AuthenticationKerberosV5
34 | BackendMessage::AuthenticationGSS
35 | BackendMessage::AuthenticationSCMCredential
36 | BackendMessage::AuthenticationGSSContinue(_)
37 | BackendMessage::AuthenticationSSPI
38 | BackendMessage::AuthenticationCleartextPassword
39 | BackendMessage::AuthenticationMD5Password(_)
40 | BackendMessage::AuthenticationSASL(_)
41 | BackendMessage::AuthenticationSASLContinue(_)
42 | BackendMessage::AuthenticationSASLFinal(_)
43 )
44 {
45 return Err(PgError::Protocol(
46 "Received authentication challenge after AuthenticationOk".to_string(),
47 ));
48 }
49 match msg {
50 BackendMessage::AuthenticationOk => {
51 if let Some(StartupAuthFlow::Scram {
52 server_final_seen: false,
53 }) = startup_auth_flow
54 {
55 return Err(PgError::Protocol(
56 "Received AuthenticationOk before AuthenticationSASLFinal".to_string(),
57 ));
58 }
59 saw_auth_ok = true;
60 }
61 BackendMessage::AuthenticationKerberosV5 => {
62 if let Some(flow) = startup_auth_flow {
63 return Err(PgError::Protocol(format!(
64 "Received AuthenticationKerberosV5 while {} authentication is in progress",
65 flow.label()
66 )));
67 }
68 startup_auth_flow = Some(StartupAuthFlow::EnterpriseGss {
69 mechanism: EnterpriseAuthMechanism::KerberosV5,
70 });
71
72 if !auth_settings.allow_kerberos_v5 {
73 return Err(PgError::Auth(
74 "Server requested Kerberos V5 authentication, but Kerberos V5 is disabled by AuthSettings".to_string(),
75 ));
76 }
77
78 if gss_token_provider.is_none() {
79 return Err(PgError::Auth(
80 "Kerberos V5 authentication requested but no GSS token provider is configured. Set ConnectOptions.gss_token_provider.".to_string(),
81 ));
82 }
83
84 let token = generate_gss_token(
85 gss_session_id,
86 EnterpriseAuthMechanism::KerberosV5,
87 None,
88 gss_token_provider.as_ref(),
89 )
90 .map_err(|e| {
91 PgError::Auth(format!("Kerberos V5 token generation failed: {}", e))
92 })?;
93
94 self.send(FrontendMessage::GSSResponse(token)).await?;
95 }
96 BackendMessage::AuthenticationGSS => {
97 if let Some(flow) = startup_auth_flow {
98 return Err(PgError::Protocol(format!(
99 "Received AuthenticationGSS while {} authentication is in progress",
100 flow.label()
101 )));
102 }
103 startup_auth_flow = Some(StartupAuthFlow::EnterpriseGss {
104 mechanism: EnterpriseAuthMechanism::GssApi,
105 });
106
107 if !auth_settings.allow_gssapi {
108 return Err(PgError::Auth(
109 "Server requested GSSAPI authentication, but GSSAPI is disabled by AuthSettings".to_string(),
110 ));
111 }
112
113 if gss_token_provider.is_none() {
114 return Err(PgError::Auth(
115 "GSSAPI authentication requested but no GSS token provider is configured. Set ConnectOptions.gss_token_provider.".to_string(),
116 ));
117 }
118
119 let token = generate_gss_token(
120 gss_session_id,
121 EnterpriseAuthMechanism::GssApi,
122 None,
123 gss_token_provider.as_ref(),
124 )
125 .map_err(|e| {
126 PgError::Auth(format!("GSSAPI initial token generation failed: {}", e))
127 })?;
128
129 self.send(FrontendMessage::GSSResponse(token)).await?;
130 }
131 BackendMessage::AuthenticationSCMCredential => {
132 if let Some(flow) = startup_auth_flow {
133 return Err(PgError::Protocol(format!(
134 "Received AuthenticationSCMCredential while {} authentication is in progress",
135 flow.label()
136 )));
137 }
138 return Err(PgError::Auth(
139 "Server requested SCM credential authentication (auth code 6). This driver currently does not support Unix-socket credential passing; use SCRAM, GSS/SSPI, or password auth for this connection."
140 .to_string(),
141 ));
142 }
143 BackendMessage::AuthenticationSSPI => {
144 if let Some(flow) = startup_auth_flow {
145 return Err(PgError::Protocol(format!(
146 "Received AuthenticationSSPI while {} authentication is in progress",
147 flow.label()
148 )));
149 }
150 startup_auth_flow = Some(StartupAuthFlow::EnterpriseGss {
151 mechanism: EnterpriseAuthMechanism::Sspi,
152 });
153
154 if !auth_settings.allow_sspi {
155 return Err(PgError::Auth(
156 "Server requested SSPI authentication, but SSPI is disabled by AuthSettings".to_string(),
157 ));
158 }
159
160 if gss_token_provider.is_none() {
161 return Err(PgError::Auth(
162 "SSPI authentication requested but no GSS token provider is configured. Set ConnectOptions.gss_token_provider.".to_string(),
163 ));
164 }
165
166 let token = generate_gss_token(
167 gss_session_id,
168 EnterpriseAuthMechanism::Sspi,
169 None,
170 gss_token_provider.as_ref(),
171 )
172 .map_err(|e| {
173 PgError::Auth(format!("SSPI initial token generation failed: {}", e))
174 })?;
175
176 self.send(FrontendMessage::GSSResponse(token)).await?;
177 }
178 BackendMessage::AuthenticationGSSContinue(server_token) => {
179 gss_roundtrips += 1;
180 if gss_roundtrips > MAX_GSS_ROUNDTRIPS {
181 return Err(PgError::Auth(format!(
182 "GSS handshake exceeded {} roundtrips — aborting",
183 MAX_GSS_ROUNDTRIPS
184 )));
185 }
186
187 let mechanism = match startup_auth_flow {
188 Some(StartupAuthFlow::EnterpriseGss { mechanism }) => mechanism,
189 Some(flow) => {
190 return Err(PgError::Protocol(format!(
191 "Received AuthenticationGSSContinue while {} authentication is in progress",
192 flow.label()
193 )));
194 }
195 None => {
196 return Err(PgError::Auth(
197 "Received GSSContinue without AuthenticationGSS/SSPI/KerberosV5 init"
198 .to_string(),
199 ));
200 }
201 };
202
203 if gss_token_provider.is_none() {
204 return Err(PgError::Auth(
205 "Received GSSContinue but no GSS token provider is configured. Set ConnectOptions.gss_token_provider.".to_string(),
206 ));
207 }
208
209 let token = generate_gss_token(
210 gss_session_id,
211 mechanism,
212 Some(&server_token),
213 gss_token_provider.as_ref(),
214 )
215 .map_err(|e| {
216 PgError::Auth(format!("GSS continue token generation failed: {}", e))
217 })?;
218
219 if !token.is_empty() {
226 self.send(FrontendMessage::GSSResponse(token)).await?;
227 }
228 }
229 BackendMessage::AuthenticationCleartextPassword => {
230 if let Some(flow) = startup_auth_flow {
231 return Err(PgError::Protocol(format!(
232 "Received AuthenticationCleartextPassword while {} authentication is in progress",
233 flow.label()
234 )));
235 }
236 startup_auth_flow = Some(StartupAuthFlow::CleartextPassword);
237
238 if !auth_settings.allow_cleartext_password {
239 return Err(PgError::Auth(
240 "Server requested cleartext authentication, but cleartext is disabled by AuthSettings"
241 .to_string(),
242 ));
243 }
244 let password = password.ok_or_else(|| {
245 PgError::Auth("Password required for cleartext authentication".to_string())
246 })?;
247 self.send(FrontendMessage::PasswordMessage(password.to_string()))
248 .await?;
249 }
250 BackendMessage::AuthenticationMD5Password(salt) => {
251 if let Some(flow) = startup_auth_flow {
252 return Err(PgError::Protocol(format!(
253 "Received AuthenticationMD5Password while {} authentication is in progress",
254 flow.label()
255 )));
256 }
257 startup_auth_flow = Some(StartupAuthFlow::Md5Password);
258
259 if !auth_settings.allow_md5_password {
260 return Err(PgError::Auth(
261 "Server requested MD5 authentication, but MD5 is disabled by AuthSettings"
262 .to_string(),
263 ));
264 }
265 let password = password.ok_or_else(|| {
266 PgError::Auth("Password required for MD5 authentication".to_string())
267 })?;
268 let md5_password = md5_password_message(user, password, salt);
269 self.send(FrontendMessage::PasswordMessage(md5_password))
270 .await?;
271 }
272 BackendMessage::AuthenticationSASL(mechanisms) => {
273 if let Some(flow) = startup_auth_flow {
274 return Err(PgError::Protocol(format!(
275 "Received AuthenticationSASL while {} authentication is in progress",
276 flow.label()
277 )));
278 }
279 startup_auth_flow = Some(StartupAuthFlow::Scram {
280 server_final_seen: false,
281 });
282
283 if !auth_settings.allow_scram_sha_256 {
284 return Err(PgError::Auth(
285 "Server requested SCRAM authentication, but SCRAM is disabled by AuthSettings"
286 .to_string(),
287 ));
288 }
289 let password = password.ok_or_else(|| {
290 PgError::Auth("Password required for SCRAM authentication".to_string())
291 })?;
292
293 let tls_binding = self.tls_server_end_point_channel_binding();
294 let (mechanism, channel_binding_data) = select_scram_mechanism(
295 &mechanisms,
296 tls_binding,
297 auth_settings.channel_binding,
298 )
299 .map_err(PgError::Auth)?;
300
301 let client = if let Some(binding_data) = channel_binding_data {
302 ScramClient::new_with_tls_server_end_point(user, password, binding_data)
303 } else {
304 ScramClient::new(user, password)
305 };
306 let first_message = client.client_first_message();
307
308 self.send(FrontendMessage::SASLInitialResponse {
309 mechanism,
310 data: first_message,
311 })
312 .await?;
313
314 scram_client = Some(client);
315 }
316 BackendMessage::AuthenticationSASLContinue(server_data) => {
317 match startup_auth_flow {
318 Some(StartupAuthFlow::Scram {
319 server_final_seen: false,
320 }) => {}
321 Some(StartupAuthFlow::Scram {
322 server_final_seen: true,
323 }) => {
324 return Err(PgError::Protocol(
325 "Received AuthenticationSASLContinue after AuthenticationSASLFinal"
326 .to_string(),
327 ));
328 }
329 Some(flow) => {
330 return Err(PgError::Protocol(format!(
331 "Received AuthenticationSASLContinue while {} authentication is in progress",
332 flow.label()
333 )));
334 }
335 None => {
336 return Err(PgError::Auth(
337 "Received SASL Continue without SASL init".to_string(),
338 ));
339 }
340 }
341
342 let client = scram_client.as_mut().ok_or_else(|| {
343 PgError::Auth("Received SASL Continue without SASL init".to_string())
344 })?;
345
346 let final_message = client
347 .process_server_first(&server_data)
348 .map_err(|e| PgError::Auth(format!("SCRAM error: {}", e)))?;
349
350 self.send(FrontendMessage::SASLResponse(final_message))
351 .await?;
352 }
353 BackendMessage::AuthenticationSASLFinal(server_signature) => {
354 match startup_auth_flow {
355 Some(StartupAuthFlow::Scram {
356 server_final_seen: false,
357 }) => {
358 startup_auth_flow = Some(StartupAuthFlow::Scram {
359 server_final_seen: true,
360 });
361 }
362 Some(StartupAuthFlow::Scram {
363 server_final_seen: true,
364 }) => {
365 return Err(PgError::Protocol(
366 "Received duplicate AuthenticationSASLFinal".to_string(),
367 ));
368 }
369 Some(flow) => {
370 return Err(PgError::Protocol(format!(
371 "Received AuthenticationSASLFinal while {} authentication is in progress",
372 flow.label()
373 )));
374 }
375 None => {
376 return Err(PgError::Auth(
377 "Received SASL Final without SASL init".to_string(),
378 ));
379 }
380 }
381
382 let client = scram_client.as_ref().ok_or_else(|| {
383 PgError::Auth("Received SASL Final without SASL init".to_string())
384 })?;
385 client
386 .verify_server_final(&server_signature)
387 .map_err(|e| PgError::Auth(format!("Server verification failed: {}", e)))?;
388 }
389 BackendMessage::ParameterStatus { .. } => {
390 if !saw_auth_ok {
391 return Err(PgError::Protocol(
392 "Received ParameterStatus before AuthenticationOk".to_string(),
393 ));
394 }
395 }
396 BackendMessage::NegotiateProtocolVersion {
397 newest_minor_supported,
398 unrecognized_protocol_options,
399 } => {
400 if saw_auth_ok {
401 return Err(PgError::Protocol(
402 "Received NegotiateProtocolVersion after AuthenticationOk".to_string(),
403 ));
404 }
405 let negotiated = if let Ok(minor) = u16::try_from(newest_minor_supported) {
406 minor
407 } else {
408 let packed = u32::try_from(newest_minor_supported).map_err(|_| {
409 PgError::Protocol(format!(
410 "Invalid NegotiateProtocolVersion newest_minor_supported: {}",
411 newest_minor_supported
412 ))
413 })?;
414 let major = (packed >> 16) as u16;
415 let minor = (packed & 0xFFFF) as u16;
416 if major != 3 {
417 return Err(PgError::Protocol(format!(
418 "Invalid NegotiateProtocolVersion newest_minor_supported: {}",
419 newest_minor_supported
420 )));
421 }
422 minor
423 };
424 if negotiated > self.requested_protocol_minor {
425 return Err(PgError::Protocol(format!(
426 "Server negotiated protocol minor {} above requested {}",
427 negotiated, self.requested_protocol_minor
428 )));
429 }
430 self.negotiated_protocol_minor = negotiated;
431 if !unrecognized_protocol_options.is_empty() {
432 tracing::debug!(
433 negotiated_minor = negotiated,
434 unrecognized_count = unrecognized_protocol_options.len(),
435 "startup_negotiate_protocol_version"
436 );
437 }
438 }
439 BackendMessage::BackendKeyData {
440 process_id,
441 secret_key,
442 } => {
443 if !saw_auth_ok {
444 return Err(PgError::Protocol(
445 "Received BackendKeyData before AuthenticationOk".to_string(),
446 ));
447 }
448 self.process_id = process_id;
449 self.cancel_key_bytes = secret_key;
450 }
451 BackendMessage::ReadyForQuery(TransactionStatus::Idle) => {
452 if !saw_auth_ok {
453 return Err(PgError::Protocol(
454 "Startup completed without AuthenticationOk".to_string(),
455 ));
456 }
457 return Ok(());
458 }
459 BackendMessage::ReadyForQuery(status) => {
460 if !saw_auth_ok {
461 return Err(PgError::Protocol(
462 "Startup completed without AuthenticationOk".to_string(),
463 ));
464 }
465 return Err(PgError::Protocol(format!(
466 "Startup completed with non-idle transaction status: {:?}",
467 status
468 )));
469 }
470 BackendMessage::ErrorResponse(err) => {
471 return Err(PgError::Connection(err.message));
472 }
473 BackendMessage::NoticeResponse(_) => {}
474 _ => {
475 return Err(PgError::Protocol(
476 "Unexpected backend message during startup".to_string(),
477 ));
478 }
479 }
480 }
481 }
482
483 fn tls_server_end_point_channel_binding(&self) -> Option<Vec<u8>> {
488 let PgStream::Tls(tls) = &self.stream else {
489 return None;
490 };
491
492 let (_, conn) = tls.get_ref();
493 let certs = conn.peer_certificates()?;
494 let leaf_cert = certs.first()?;
495
496 let mut hasher = Sha256::new();
497 hasher.update(leaf_cert.as_ref());
498 Some(hasher.finalize().to_vec())
499 }
500
501 pub async fn close(mut self) -> PgResult<()> {
504 use crate::protocol::PgEncoder;
505
506 let terminate = PgEncoder::encode_terminate();
508 self.write_all_with_timeout(&terminate, "stream write")
509 .await?;
510 self.flush_with_timeout("stream flush").await?;
511
512 Ok(())
513 }
514
515 pub(crate) const MAX_PREPARED_PER_CONN: usize = 128;
521
522 pub(crate) fn evict_prepared_if_full(&mut self) {
528 if self.prepared_statements.len() >= Self::MAX_PREPARED_PER_CONN {
529 if let Some((evicted_hash, evicted_name)) = self.stmt_cache.pop_lru() {
531 self.prepared_statements.remove(&evicted_name);
532 self.column_info_cache.remove(&evicted_hash);
533 self.pending_statement_closes.push(evicted_name);
534 } else {
535 if let Some(key) = self.prepared_statements.keys().next().cloned() {
539 self.prepared_statements.remove(&key);
540 self.pending_statement_closes.push(key);
541 }
542 }
543 }
544 }
545
546 pub(crate) fn clear_prepared_statement_state(&mut self) {
551 self.stmt_cache.clear();
552 self.prepared_statements.clear();
553 self.column_info_cache.clear();
554 self.pending_statement_closes.clear();
555 }
556}