Skip to main content

io_smtp/rfc4954/
auth_data.rs

1//! SMTP AUTH continuation data (RFC 4954 ยง4).
2
3use alloc::{
4    boxed::Box,
5    string::{String, ToString},
6    vec::Vec,
7};
8
9use base64::{Engine, engine::general_purpose::STANDARD as base64};
10use secrecy::{ExposeSecret, SecretBox};
11use thiserror::Error;
12
13/// Errors that can occur while parsing auth data.
14#[derive(Debug, Error)]
15pub enum SmtpAuthDataError {
16    /// The input carries no terminating CRLF yet.
17    #[error("Parse SMTP auth data error: incomplete input")]
18    Incomplete,
19    /// The base64 payload could not be decoded.
20    #[error("Parse SMTP auth data error: {0}")]
21    Base64(String),
22}
23
24/// Data line used during SMTP AUTH exchange.
25///
26/// Holds the raw binary data, i.e., a `Vec<u8>`, *not* the BASE64
27/// string.
28#[derive(Debug)]
29pub enum SmtpAuthData {
30    /// Continue SASL authentication with response data.
31    Continue(SecretBox<[u8]>),
32    /// Cancel SASL authentication.
33    ///
34    /// The client sends a single `*` to cancel the authentication
35    /// exchange.
36    Cancel,
37}
38
39impl SmtpAuthData {
40    /// Create a continuation response with the given data.
41    pub fn r#continue(data: impl Into<Box<[u8]>>) -> Self {
42        Self::Continue(SecretBox::new(data.into()))
43    }
44
45    /// Returns true if `buf` contains a complete auth data line.
46    pub fn is_complete(buf: &[u8]) -> bool {
47        buf.ends_with(b"\r\n")
48    }
49
50    /// Parse auth data from bytes.
51    pub fn parse(input: &[u8]) -> Result<SmtpAuthData, SmtpAuthDataError> {
52        if !input.ends_with(b"\r\n") {
53            return Err(SmtpAuthDataError::Incomplete);
54        }
55
56        let line = &input[..input.len() - 2];
57        if line == b"*" {
58            return Ok(SmtpAuthData::Cancel);
59        }
60
61        let decoded = base64
62            .decode(line)
63            .map_err(|e| SmtpAuthDataError::Base64(e.to_string()))?;
64
65        Ok(SmtpAuthData::r#continue(decoded.into_boxed_slice()))
66    }
67}
68
69impl From<SmtpAuthData> for Vec<u8> {
70    fn from(data: SmtpAuthData) -> Vec<u8> {
71        let mut buf = Vec::new();
72
73        match data {
74            SmtpAuthData::Continue(secret) => {
75                buf.extend_from_slice(base64.encode(secret.expose_secret()).as_bytes());
76            }
77            SmtpAuthData::Cancel => buf.push(b'*'),
78        }
79
80        buf.extend_from_slice(b"\r\n");
81        buf
82    }
83}