1use std::io;
4
5use bytes::{Buf, Bytes};
6
7use crate::{
8 Conn,
9 auth::Ready,
10 codec::{
11 Authentication, BackendMessage, DiagnosticResponse, Frame, FrontendMessage,
12 NegotiateProtocolVersion, TransactionStatus,
13 },
14 grammar::server_authentication as auth_grammar,
15 pre_startup::{Startup, Terminated},
16 startup::{ProtocolVersion, StartupMessage},
17};
18
19#[derive(Debug)]
20pub enum ServerAuth {}
22
23#[derive(Debug)]
24pub enum ServerStartupValidated {}
26
27#[derive(Debug)]
28pub enum ServerStartupRejected {}
30
31#[derive(Debug)]
32pub enum ServerPassword {}
34
35#[derive(Debug)]
36pub enum ServerSaslInitial {}
38
39#[derive(Debug)]
40pub enum ServerSasl {}
42
43#[derive(Debug)]
44pub enum ServerAuthResponse {}
46
47#[derive(Debug)]
48pub enum ServerStartupReady {}
50
51#[derive(Debug)]
53pub enum ServerProtocolOffer<S, C> {
54 Supported {
56 conn: Conn<S, ServerStartupValidated, C>,
58 message: StartupMessage,
60 negotiate_to: Option<ProtocolVersion>,
62 },
63 Rejected {
65 conn: Conn<S, ServerStartupRejected, C>,
67 message: StartupMessage,
69 },
70}
71
72#[derive(Clone, Eq, PartialEq)]
74pub struct SaslInitialResponse {
75 pub mechanism: Bytes,
77 pub response: Option<Bytes>,
79}
80
81impl std::fmt::Debug for SaslInitialResponse {
82 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 formatter
84 .debug_struct("SaslInitialResponse")
85 .field("mechanism", &self.mechanism)
86 .field("response", &self.response.as_ref().map(Bytes::len))
87 .finish()
88 }
89}
90
91pub type ServerProjection<T, S, Phase, C> = Result<T, Box<(Conn<S, Phase, C>, FrontendMessage)>>;
93
94pub type PasswordProjection<S, C> =
96 ServerProjection<(Conn<S, ServerAuth, C>, Bytes), S, ServerPassword, C>;
97
98pub type SaslInitialProjection<S, C> =
100 ServerProjection<(Conn<S, ServerSasl, C>, SaslInitialResponse), S, ServerSaslInitial, C>;
101
102impl<S, C> Conn<S, Startup, C> {
103 pub fn validate_protocol(
105 self,
106 message: StartupMessage,
107 newest: ProtocolVersion,
108 ) -> ServerProtocolOffer<S, C> {
109 if message.version.major == newest.major {
110 let negotiate_to = (message.version.minor > newest.minor).then_some(newest);
111 ServerProtocolOffer::Supported {
112 conn: self.transition(),
113 message,
114 negotiate_to,
115 }
116 } else {
117 ServerProtocolOffer::Rejected {
118 conn: self.transition(),
119 message,
120 }
121 }
122 }
123}
124
125impl<S, C> Conn<S, ServerStartupValidated, C> {
126 pub fn begin_server_auth(self) -> Conn<S, ServerAuth, C> {
128 self.transition()
129 }
130}
131
132impl<S, C> Conn<S, ServerStartupRejected, C> {
133 pub fn error(
139 self,
140 response: DiagnosticResponse,
141 ) -> io::Result<(Conn<S, Terminated, C>, Frame)> {
142 Ok((
143 self.transition(),
144 BackendMessage::ErrorResponse(response).to_frame()?,
145 ))
146 }
147}
148
149impl<S, C> Conn<S, ServerAuth, C> {
150 pub fn request_cleartext(self) -> io::Result<(Conn<S, ServerPassword, C>, Frame)> {
156 Ok((
157 self.transition(),
158 authentication_frame(Authentication::CleartextPassword)?,
159 ))
160 }
161
162 pub fn request_md5(self, salt: [u8; 4]) -> io::Result<(Conn<S, ServerPassword, C>, Frame)> {
168 Ok((
169 self.transition(),
170 authentication_frame(Authentication::Md5Password { salt })?,
171 ))
172 }
173
174 pub fn request_sasl(
180 self,
181 mechanisms: Vec<Bytes>,
182 ) -> io::Result<(Conn<S, ServerSaslInitial, C>, Frame)> {
183 Ok((
184 self.transition(),
185 authentication_frame(Authentication::Sasl { mechanisms })?,
186 ))
187 }
188
189 pub fn request_kerberos_v5(self) -> io::Result<(Conn<S, ServerAuthResponse, C>, Frame)> {
195 Ok((
196 self.transition(),
197 authentication_frame(Authentication::KerberosV5)?,
198 ))
199 }
200
201 pub fn request_gss(self) -> io::Result<(Conn<S, ServerAuthResponse, C>, Frame)> {
207 Ok((
208 self.transition(),
209 authentication_frame(Authentication::Gss)?,
210 ))
211 }
212
213 pub fn request_sspi(self) -> io::Result<(Conn<S, ServerAuthResponse, C>, Frame)> {
219 Ok((
220 self.transition(),
221 authentication_frame(Authentication::Sspi)?,
222 ))
223 }
224
225 pub fn authentication_ok(self) -> io::Result<(Conn<S, ServerStartupReady, C>, Frame)> {
231 Ok((self.transition(), authentication_frame(Authentication::Ok)?))
232 }
233}
234
235impl<S, C> Conn<S, ServerPassword, C> {
236 pub fn receive_password(self, message: FrontendMessage) -> PasswordProjection<S, C> {
242 match (
243 auth_grammar::project_external(auth_grammar::RuntimeState::PasswordResponse, &message),
244 message,
245 ) {
246 (Some(auth_grammar::Event::Response), FrontendMessage::PasswordResponse(body)) => {
247 match password_body(body) {
248 Ok(password) => Ok((self.transition(), password)),
249 Err(body) => Err(Box::new((self, FrontendMessage::PasswordResponse(body)))),
250 }
251 }
252 (_, other) => Err(Box::new((self, other))),
253 }
254 }
255}
256
257impl<S, C> Conn<S, ServerSaslInitial, C> {
258 pub fn receive_initial(self, message: FrontendMessage) -> SaslInitialProjection<S, C> {
264 match (
265 auth_grammar::project_external(auth_grammar::RuntimeState::SaslInitial, &message),
266 message,
267 ) {
268 (Some(auth_grammar::Event::Initial), FrontendMessage::PasswordResponse(body)) => {
269 match sasl_initial(body) {
270 Ok(initial) => Ok((self.transition(), initial)),
271 Err(body) => Err(Box::new((self, FrontendMessage::PasswordResponse(body)))),
272 }
273 }
274 (_, other) => Err(Box::new((self, other))),
275 }
276 }
277}
278
279impl<S, C> Conn<S, ServerSasl, C> {
280 pub fn receive_response(
286 self,
287 message: FrontendMessage,
288 ) -> ServerProjection<(Self, Bytes), S, ServerSasl, C> {
289 match (
290 auth_grammar::project_external(auth_grammar::RuntimeState::SaslResponse, &message),
291 message,
292 ) {
293 (Some(auth_grammar::Event::Response), FrontendMessage::PasswordResponse(response)) => {
294 Ok((self, response))
295 }
296 (_, other) => Err(Box::new((self, other))),
297 }
298 }
299
300 pub fn continue_with(self, challenge: Bytes) -> io::Result<(Self, Frame)> {
306 Ok((
307 self,
308 authentication_frame(Authentication::SaslContinue(challenge))?,
309 ))
310 }
311
312 pub fn finish(self, server_final: Bytes) -> io::Result<(Conn<S, ServerAuth, C>, Frame)> {
318 Ok((
319 self.transition(),
320 authentication_frame(Authentication::SaslFinal(server_final))?,
321 ))
322 }
323}
324
325impl<S, C> Conn<S, ServerAuthResponse, C> {
326 pub fn receive_response(
332 self,
333 message: FrontendMessage,
334 ) -> ServerProjection<(Self, Bytes), S, ServerAuthResponse, C> {
335 match (
336 auth_grammar::project_external(auth_grammar::RuntimeState::TokenResponse, &message),
337 message,
338 ) {
339 (Some(auth_grammar::Event::Response), FrontendMessage::PasswordResponse(response)) => {
340 Ok((self, response))
341 }
342 (_, other) => Err(Box::new((self, other))),
343 }
344 }
345
346 pub fn continue_gss(self, token: Bytes) -> io::Result<(Self, Frame)> {
352 Ok((
353 self,
354 authentication_frame(Authentication::GssContinue(token))?,
355 ))
356 }
357
358 pub fn verified(self) -> Conn<S, ServerAuth, C> {
360 self.transition()
361 }
362}
363
364impl<S, C> Conn<S, ServerStartupReady, C> {
365 pub fn parameter_status(self, name: Bytes, value: Bytes) -> io::Result<(Self, Frame)> {
371 Ok((
372 self,
373 BackendMessage::ParameterStatus { name, value }.to_frame()?,
374 ))
375 }
376
377 pub fn backend_key_data(self, process_id: u32, secret_key: Bytes) -> io::Result<(Self, Frame)> {
383 if !(4..=256).contains(&secret_key.len()) {
384 return Err(io::Error::new(
385 io::ErrorKind::InvalidInput,
386 "cancellation key length is outside 4..=256",
387 ));
388 }
389 Ok((
390 self,
391 BackendMessage::BackendKeyData {
392 process_id,
393 secret_key,
394 }
395 .to_frame()?,
396 ))
397 }
398
399 pub fn negotiate_protocol(
405 self,
406 newest: ProtocolVersion,
407 unsupported_options: Vec<Bytes>,
408 ) -> io::Result<(Self, Frame)> {
409 Ok((
410 self,
411 BackendMessage::NegotiateProtocolVersion(NegotiateProtocolVersion {
412 newest,
413 unsupported_options,
414 })
415 .to_frame()?,
416 ))
417 }
418
419 pub fn ready(self) -> io::Result<(Conn<S, Ready, C>, Frame)> {
425 Ok((
426 self.transition(),
427 BackendMessage::ReadyForQuery(TransactionStatus::Idle).to_frame()?,
428 ))
429 }
430}
431
432fn authentication_frame(authentication: Authentication) -> io::Result<Frame> {
433 BackendMessage::Authentication(authentication).to_frame()
434}
435
436fn password_body(body: Bytes) -> Result<Bytes, Bytes> {
437 if body.last() == Some(&0) && !body[..body.len() - 1].contains(&0) {
438 Ok(body.slice(..body.len() - 1))
439 } else {
440 Err(body)
441 }
442}
443
444fn sasl_initial(mut body: Bytes) -> Result<SaslInitialResponse, Bytes> {
445 let original = body.clone();
446 let Some(nul) = body.iter().position(|byte| *byte == 0) else {
447 return Err(original);
448 };
449 let mechanism = body.split_to(nul);
450 body.advance(1);
451 if body.len() < 4 {
452 return Err(original);
453 }
454 let length = body.get_i32();
455 if length == -1 && body.is_empty() {
456 return Ok(SaslInitialResponse {
457 mechanism,
458 response: None,
459 });
460 }
461 let Ok(length) = usize::try_from(length) else {
462 return Err(original);
463 };
464 if body.len() != length {
465 return Err(original);
466 }
467 Ok(SaslInitialResponse {
468 mechanism,
469 response: Some(body),
470 })
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476 use crate::{
477 Pristine,
478 scram::{SCRAM_SHA_256, ScramServer, ServerChannelBinding},
479 };
480 use bytes::{BufMut as _, BytesMut};
481 use postgres_protocol::authentication::sasl::{ChannelBinding, ScramSha256};
482 use std::collections::BTreeMap;
483
484 fn validated_startup() -> Conn<(), ServerStartupValidated, Pristine> {
485 let startup: Conn<(), Startup, Pristine> = Conn::new(()).transition();
486 let message = StartupMessage {
487 version: ProtocolVersion::V3_2,
488 parameters: BTreeMap::new(),
489 };
490 let ServerProtocolOffer::Supported { conn, .. } =
491 startup.validate_protocol(message, ProtocolVersion::V3_2)
492 else {
493 panic!("supported protocol was rejected")
494 };
495 conn
496 }
497
498 #[test]
499 fn cleartext_response_returns_to_independent_policy_choice() {
500 let (password, request) = validated_startup()
501 .begin_server_auth()
502 .request_cleartext()
503 .unwrap();
504 assert_eq!(
505 request,
506 authentication_frame(Authentication::CleartextPassword).unwrap()
507 );
508
509 let (auth, response) = password
510 .receive_password(FrontendMessage::PasswordResponse(Bytes::from_static(
511 b"secret\0",
512 )))
513 .unwrap();
514 assert_eq!(response, Bytes::from_static(b"secret"));
515 let (ready, ok) = auth.authentication_ok().unwrap();
516 assert_eq!(ok, authentication_frame(Authentication::Ok).unwrap());
517 ready.into_transport();
518 }
519
520 #[test]
521 fn sasl_is_a_recursive_server_sub_session() {
522 let (initial, _) = validated_startup()
523 .begin_server_auth()
524 .request_sasl(vec![Bytes::from_static(b"SCRAM-SHA-256")])
525 .unwrap();
526 let body = Bytes::from_static(b"SCRAM-SHA-256\0\0\0\0\x03one");
527 let (sasl, initial) = initial
528 .receive_initial(FrontendMessage::PasswordResponse(body))
529 .unwrap();
530 assert_eq!(initial.response, Some(Bytes::from_static(b"one")));
531 let (sasl, _) = sasl
532 .continue_with(Bytes::from_static(b"challenge"))
533 .unwrap();
534 let (sasl, response) = sasl
535 .receive_response(FrontendMessage::PasswordResponse(Bytes::from_static(
536 b"two",
537 )))
538 .unwrap();
539 assert_eq!(response, Bytes::from_static(b"two"));
540 let (auth, _) = sasl.finish(Bytes::from_static(b"verified")).unwrap();
541 auth.into_transport();
542 }
543
544 #[test]
545 fn startup_completion_mints_keys_and_requires_ready() {
546 let (startup_ready, _) = validated_startup()
547 .begin_server_auth()
548 .authentication_ok()
549 .unwrap();
550 let (startup_ready, parameter) = startup_ready
551 .parameter_status(
552 Bytes::from_static(b"server_version"),
553 Bytes::from_static(b"18"),
554 )
555 .unwrap();
556 assert_eq!(parameter.tag, b'S');
557 let (startup_ready, key) = startup_ready
558 .backend_key_data(42, Bytes::from_static(b"secret-key"))
559 .unwrap();
560 assert_eq!(key.tag, b'K');
561 let (ready, frame) = startup_ready.ready().unwrap();
562 assert_eq!(frame.body, Bytes::from_static(b"I"));
563 ready.into_transport();
564 }
565
566 #[test]
567 fn protocol_validation_negotiates_minor_and_rejects_major() {
568 let startup: Conn<(), Startup> = Conn::new(()).transition();
569 let message = StartupMessage {
570 version: ProtocolVersion { major: 3, minor: 9 },
571 parameters: BTreeMap::new(),
572 };
573 let ServerProtocolOffer::Supported {
574 conn, negotiate_to, ..
575 } = startup.validate_protocol(message, ProtocolVersion::V3_2)
576 else {
577 panic!("compatible major was rejected")
578 };
579 assert_eq!(negotiate_to, Some(ProtocolVersion::V3_2));
580 conn.into_transport();
581
582 let startup: Conn<(), Startup> = Conn::new(()).transition();
583 let message = StartupMessage {
584 version: ProtocolVersion { major: 4, minor: 0 },
585 parameters: BTreeMap::new(),
586 };
587 let ServerProtocolOffer::Rejected { conn, .. } =
588 startup.validate_protocol(message, ProtocolVersion::V3_2)
589 else {
590 panic!("unsupported major was accepted")
591 };
592 conn.into_transport();
593 }
594
595 #[test]
596 fn scram_server_engine_completes_the_typed_authentication_session() {
597 use crate::grammar::server_authentication::{Event, RuntimeFsm, RuntimeState};
598
599 let mut generated = RuntimeFsm::new();
600 generated.step(Event::Begin).unwrap();
601 let (initial_state, offer_frame) = validated_startup()
602 .begin_server_auth()
603 .request_sasl(vec![Bytes::from_static(SCRAM_SHA_256)])
604 .unwrap();
605 generated.step(Event::Sasl).unwrap();
606 assert_eq!(offer_frame.tag, b'R');
607
608 let mut client = ScramSha256::new(b"secret", ChannelBinding::unsupported());
609 let mut initial_body = BytesMut::new();
610 initial_body.extend_from_slice(SCRAM_SHA_256);
611 initial_body.put_u8(0);
612 initial_body.put_i32(i32::try_from(client.message().len()).unwrap());
613 initial_body.extend_from_slice(client.message());
614 let (sasl, initial) = initial_state
615 .receive_initial(FrontendMessage::PasswordResponse(initial_body.freeze()))
616 .unwrap();
617 generated.step(Event::Initial).unwrap();
618
619 let verifier = ScramServer::with_parameters(
620 b"secret",
621 b"fixed test salt".to_vec(),
622 crate::scram::DEFAULT_ITERATIONS,
623 ServerChannelBinding::None,
624 )
625 .unwrap();
626 let (exchange, challenge) = verifier
627 .start(&initial.mechanism, initial.response.as_deref().unwrap())
628 .unwrap();
629 let (sasl, challenge_frame) = sasl.continue_with(challenge.clone()).unwrap();
630 generated.step(Event::Continue).unwrap();
631 assert_eq!(challenge_frame.tag, b'R');
632 client.update(&challenge).unwrap();
633
634 let (sasl, response) = sasl
635 .receive_response(FrontendMessage::PasswordResponse(Bytes::copy_from_slice(
636 client.message(),
637 )))
638 .unwrap();
639 generated.step(Event::Response).unwrap();
640 let server_final = exchange.finish(&response).unwrap();
641 client.finish(&server_final).unwrap();
642 let (auth, final_frame) = sasl.finish(server_final).unwrap();
643 generated.step(Event::Final).unwrap();
644 assert_eq!(final_frame.tag, b'R');
645 let (startup_ready, ok) = auth.authentication_ok().unwrap();
646 generated.step(Event::Ok).unwrap();
647 assert_eq!(ok.tag, b'R');
648 assert_eq!(generated.state(), RuntimeState::StartupReady);
649 startup_ready.into_transport();
650 }
651}