zero_postgres/protocol/backend/
auth.rs

1//! Authentication-related backend messages.
2
3use zerocopy::{FromBytes, Immutable, KnownLayout};
4
5use crate::error::{Error, Result};
6use crate::protocol::codec::{read_cstr, read_i32, read_u32};
7use crate::protocol::types::TransactionStatus;
8
9/// Authentication method constants.
10pub mod auth_type {
11    pub const OK: i32 = 0;
12    pub const KERBEROS_V5: i32 = 2;
13    pub const CLEARTEXT_PASSWORD: i32 = 3;
14    pub const MD5_PASSWORD: i32 = 5;
15    pub const GSS: i32 = 7;
16    pub const GSS_CONTINUE: i32 = 8;
17    pub const SSPI: i32 = 9;
18    pub const SASL: i32 = 10;
19    pub const SASL_CONTINUE: i32 = 11;
20    pub const SASL_FINAL: i32 = 12;
21}
22
23/// Authentication message from the server.
24#[derive(Debug)]
25pub enum AuthenticationMessage<'a> {
26    /// Authentication successful
27    Ok,
28    /// Kerberos V5 authentication required
29    KerberosV5,
30    /// Cleartext password required
31    CleartextPassword,
32    /// MD5 password required (with 4-byte salt)
33    Md5Password { salt: [u8; 4] },
34    /// GSS authentication
35    Gss,
36    /// GSS continue (with additional data)
37    GssContinue { data: &'a [u8] },
38    /// SSPI authentication
39    Sspi,
40    /// SASL authentication required (with list of mechanisms)
41    Sasl { mechanisms: Vec<&'a str> },
42    /// SASL continue (with server-first-message)
43    SaslContinue { data: &'a [u8] },
44    /// SASL final (with server-final-message)
45    SaslFinal { data: &'a [u8] },
46}
47
48impl<'a> AuthenticationMessage<'a> {
49    /// Parse an Authentication message from payload bytes.
50    pub fn parse(payload: &'a [u8]) -> Result<Self> {
51        let (auth_type, rest) = read_i32(payload)?;
52
53        match auth_type {
54            auth_type::OK => Ok(AuthenticationMessage::Ok),
55            auth_type::KERBEROS_V5 => Ok(AuthenticationMessage::KerberosV5),
56            auth_type::CLEARTEXT_PASSWORD => Ok(AuthenticationMessage::CleartextPassword),
57            auth_type::MD5_PASSWORD => {
58                if rest.len() < 4 {
59                    return Err(Error::Protocol("MD5Password: missing salt".into()));
60                }
61                let mut salt = [0u8; 4];
62                salt.copy_from_slice(&rest[..4]);
63                Ok(AuthenticationMessage::Md5Password { salt })
64            }
65            auth_type::GSS => Ok(AuthenticationMessage::Gss),
66            auth_type::GSS_CONTINUE => Ok(AuthenticationMessage::GssContinue { data: rest }),
67            auth_type::SSPI => Ok(AuthenticationMessage::Sspi),
68            auth_type::SASL => {
69                let mut mechanisms = Vec::new();
70                let mut data = rest;
71                while !data.is_empty() && data[0] != 0 {
72                    let (mechanism, remaining) = read_cstr(data)?;
73                    mechanisms.push(mechanism);
74                    data = remaining;
75                }
76                Ok(AuthenticationMessage::Sasl { mechanisms })
77            }
78            auth_type::SASL_CONTINUE => Ok(AuthenticationMessage::SaslContinue { data: rest }),
79            auth_type::SASL_FINAL => Ok(AuthenticationMessage::SaslFinal { data: rest }),
80            _ => Err(Error::Protocol(format!(
81                "Unknown authentication type: {}",
82                auth_type
83            ))),
84        }
85    }
86}
87
88/// BackendKeyData message - contains process ID and secret key for cancellation.
89///
90/// In protocol 3.2, the secret key is variable-length (4-256 bytes).
91#[derive(Debug, Clone)]
92pub struct BackendKeyData {
93    /// Process ID of the backend
94    pid: u32,
95    /// Secret key for cancellation (variable length in protocol 3.2)
96    secret_key: Vec<u8>,
97}
98
99impl BackendKeyData {
100    /// Parse a BackendKeyData message from payload bytes.
101    pub fn parse(payload: &[u8]) -> Result<Self> {
102        if payload.len() < 4 {
103            return Err(Error::Protocol("BackendKeyData: payload too short".into()));
104        }
105        let (pid, rest) = read_u32(payload)?;
106        if rest.len() < 4 || rest.len() > 256 {
107            return Err(Error::Protocol(format!(
108                "BackendKeyData: invalid secret key length {}",
109                rest.len()
110            )));
111        }
112        Ok(Self {
113            pid,
114            secret_key: rest.to_vec(),
115        })
116    }
117
118    /// Get the process ID.
119    pub fn process_id(&self) -> u32 {
120        self.pid
121    }
122
123    /// Get the secret key bytes.
124    pub fn secret_key(&self) -> &[u8] {
125        &self.secret_key
126    }
127}
128
129/// ParameterStatus message - server parameter name and value.
130#[derive(Debug, Clone)]
131pub struct ParameterStatus<'a> {
132    /// Parameter name
133    pub name: &'a str,
134    /// Parameter value
135    pub value: &'a str,
136}
137
138impl<'a> ParameterStatus<'a> {
139    /// Parse a ParameterStatus message from payload bytes.
140    pub fn parse(payload: &'a [u8]) -> Result<Self> {
141        let (name, rest) = read_cstr(payload)?;
142        let (value, _) = read_cstr(rest)?;
143        Ok(Self { name, value })
144    }
145}
146
147/// ReadyForQuery message - indicates server is ready for a new query.
148#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable)]
149#[repr(C, packed)]
150pub struct ReadyForQuery {
151    /// Transaction status byte
152    pub status: u8,
153}
154
155impl ReadyForQuery {
156    /// Parse a ReadyForQuery message from payload bytes.
157    pub fn parse(payload: &[u8]) -> Result<&Self> {
158        Self::ref_from_bytes(payload).map_err(|e| Error::Protocol(format!("ReadyForQuery: {e:?}")))
159    }
160
161    /// Get the transaction status.
162    pub fn transaction_status(&self) -> Option<TransactionStatus> {
163        TransactionStatus::from_byte(self.status)
164    }
165}
166
167/// NotificationResponse message - asynchronous notification from LISTEN/NOTIFY.
168#[derive(Debug, Clone)]
169pub struct NotificationResponse<'a> {
170    /// PID of the notifying backend
171    pub pid: u32,
172    /// Channel name
173    pub channel: &'a str,
174    /// Notification payload
175    pub payload: &'a str,
176}
177
178impl<'a> NotificationResponse<'a> {
179    /// Parse a NotificationResponse message from payload bytes.
180    pub fn parse(payload: &'a [u8]) -> Result<Self> {
181        let (pid, rest) = read_u32(payload)?;
182        let (channel, rest) = read_cstr(rest)?;
183        let (payload_str, _) = read_cstr(rest)?;
184        Ok(Self {
185            pid,
186            channel,
187            payload: payload_str,
188        })
189    }
190}
191
192/// NegotiateProtocolVersion message - server doesn't support requested protocol features.
193#[derive(Debug, Clone)]
194pub struct NegotiateProtocolVersion<'a> {
195    /// Newest minor protocol version supported
196    pub newest_minor_version: u32,
197    /// Unrecognized protocol options
198    pub unrecognized_options: Vec<&'a str>,
199}
200
201impl<'a> NegotiateProtocolVersion<'a> {
202    /// Parse a NegotiateProtocolVersion message from payload bytes.
203    pub fn parse(payload: &'a [u8]) -> Result<Self> {
204        let (newest_minor_version, rest) = read_u32(payload)?;
205        let (num_options, mut rest) = read_u32(rest)?;
206
207        let mut unrecognized_options = Vec::with_capacity(num_options as usize);
208        for _ in 0..num_options {
209            let (option, remaining) = read_cstr(rest)?;
210            unrecognized_options.push(option);
211            rest = remaining;
212        }
213
214        Ok(Self {
215            newest_minor_version,
216            unrecognized_options,
217        })
218    }
219}