sqlx/sqlx-postgres/src/message/authentication.rs
Austin Bonander b5312c3b6f Break drivers out into separate crates, clean up some technical debt (#2039)
* WIP rt refactors

* refactor: break drivers out into separate crates

also cleans up significant technical debt
2023-02-21 13:25:25 -08:00

190 lines
5.5 KiB
Rust

use std::str::from_utf8;
use memchr::memchr;
use sqlx_core::bytes::{Buf, Bytes};
use crate::error::Error;
use crate::io::Decode;
// On startup, the server sends an appropriate authentication request message,
// to which the frontend must reply with an appropriate authentication
// response message (such as a password).
// For all authentication methods except GSSAPI, SSPI and SASL, there is at
// most one request and one response. In some methods, no response at all is
// needed from the frontend, and so no authentication request occurs.
// For GSSAPI, SSPI and SASL, multiple exchanges of packets may
// be needed to complete the authentication.
// <https://www.postgresql.org/docs/devel/protocol-flow.html#id-1.10.5.7.3>
// <https://www.postgresql.org/docs/devel/protocol-message-formats.html>
#[derive(Debug)]
pub enum Authentication {
/// The authentication exchange is successfully completed.
Ok,
/// The frontend must now send a [PasswordMessage] containing the
/// password in clear-text form.
CleartextPassword,
/// The frontend must now send a [PasswordMessage] containing the
/// password (with user name) encrypted via MD5, then encrypted
/// again using the 4-byte random salt.
Md5Password(AuthenticationMd5Password),
/// The frontend must now initiate a SASL negotiation,
/// using one of the SASL mechanisms listed in the message.
///
/// The frontend will send a [SaslInitialResponse] with the name
/// of the selected mechanism, and the first part of the SASL
/// data stream in response to this.
///
/// If further messages are needed, the server will
/// respond with [Authentication::SaslContinue].
Sasl(AuthenticationSasl),
/// This message contains challenge data from the previous step of SASL negotiation.
///
/// The frontend must respond with a [SaslResponse] message.
SaslContinue(AuthenticationSaslContinue),
/// SASL authentication has completed with additional mechanism-specific
/// data for the client.
///
/// The server will next send [Authentication::Ok] to
/// indicate successful authentication.
SaslFinal(AuthenticationSaslFinal),
}
impl Decode<'_> for Authentication {
fn decode_with(mut buf: Bytes, _: ()) -> Result<Self, Error> {
Ok(match buf.get_u32() {
0 => Authentication::Ok,
3 => Authentication::CleartextPassword,
5 => {
let mut salt = [0; 4];
buf.copy_to_slice(&mut salt);
Authentication::Md5Password(AuthenticationMd5Password { salt })
}
10 => Authentication::Sasl(AuthenticationSasl(buf)),
11 => Authentication::SaslContinue(AuthenticationSaslContinue::decode(buf)?),
12 => Authentication::SaslFinal(AuthenticationSaslFinal::decode(buf)?),
ty => {
return Err(err_protocol!("unknown authentication method: {}", ty));
}
})
}
}
/// Body of [Authentication::Md5Password].
#[derive(Debug)]
pub struct AuthenticationMd5Password {
pub salt: [u8; 4],
}
/// Body of [Authentication::Sasl].
#[derive(Debug)]
pub struct AuthenticationSasl(Bytes);
impl AuthenticationSasl {
#[inline]
pub fn mechanisms(&self) -> SaslMechanisms<'_> {
SaslMechanisms(&self.0)
}
}
/// An iterator over the SASL authentication mechanisms provided by the server.
pub struct SaslMechanisms<'a>(&'a [u8]);
impl<'a> Iterator for SaslMechanisms<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
if !self.0.is_empty() && self.0[0] == b'\0' {
return None;
}
let mechanism = memchr(b'\0', self.0).and_then(|nul| from_utf8(&self.0[..nul]).ok())?;
self.0 = &self.0[(mechanism.len() + 1)..];
Some(mechanism)
}
}
#[derive(Debug)]
pub struct AuthenticationSaslContinue {
pub salt: Vec<u8>,
pub iterations: u32,
pub nonce: String,
pub message: String,
}
impl Decode<'_> for AuthenticationSaslContinue {
fn decode_with(buf: Bytes, _: ()) -> Result<Self, Error> {
let mut iterations: u32 = 4096;
let mut salt = Vec::new();
let mut nonce = Bytes::new();
// [Example]
// r=/z+giZiTxAH7r8sNAeHr7cvpqV3uo7G/bJBIJO3pjVM7t3ng,s=4UV68bIkC8f9/X8xH7aPhg==,i=4096
for item in buf.split(|b| *b == b',') {
let key = item[0];
let value = &item[2..];
match key {
b'r' => {
nonce = buf.slice_ref(value);
}
b'i' => {
iterations = atoi::atoi(value).unwrap_or(4096);
}
b's' => {
salt = base64::decode(value).map_err(Error::protocol)?;
}
_ => {}
}
}
Ok(Self {
iterations,
salt,
nonce: from_utf8(&*nonce).map_err(Error::protocol)?.to_owned(),
message: from_utf8(&*buf).map_err(Error::protocol)?.to_owned(),
})
}
}
#[derive(Debug)]
pub struct AuthenticationSaslFinal {
pub verifier: Vec<u8>,
}
impl Decode<'_> for AuthenticationSaslFinal {
fn decode_with(buf: Bytes, _: ()) -> Result<Self, Error> {
let mut verifier = Vec::new();
for item in buf.split(|b| *b == b',') {
let key = item[0];
let value = &item[2..];
if let b'v' = key {
verifier = base64::decode(value).map_err(Error::protocol)?;
}
}
Ok(Self { verifier })
}
}