async_pop/
sasl.rs

1/*!
2# Sasl
3
4This module provides support for SASL (Simple Authentication and Security Layer), as specified in [RFC4422](https://datatracker.ietf.org/doc/html/rfc4422)
5
6It allows one to use these mechanisms to authenticate with a Pop3 compatible server and implement more mechanisms if they are needed.
7
8The mechanisms for PLAIN and XOAUTH2 are already present as they are commonly used.
9
10Implementing a mechanism is simple:
11
12```rust,ignore
13pub struct MyAuthenticator {
14    username: String,
15    secret_token: String,
16}
17
18impl Authenticator for MyAuthenticator {
19    fn mechanism(&self) -> &str {
20        "SUPER_COOL_MECHANISM"
21    }
22
23    fn auth(&self) -> Option<String> {
24        // Specify your cool format
25        Some(format!("\x00{}\x00{}", self.username, self.secret_token))
26    }
27
28    async fn handle<'a, S: Read + Write + Unpin + Send>(
29        &self,
30        communicator: Communicator<'a, S>,
31    ) -> Result<()> {
32        let challenge = communicator.next_challenge().await?;
33
34        let response = mechanism_lib::handle_challenge(challenge)?;
35
36        communicator.send(response).await?;
37
38        Ok(())
39    }
40}
41```
42*/
43
44use std::collections::VecDeque;
45
46use async_trait::async_trait;
47
48use crate::{
49    command::Command,
50    error::{err, ErrorKind, Result},
51    request::Request,
52    response::{types::message::Text, Response},
53    runtime::io::{Read, Write},
54    stream::PopStream,
55};
56
57/// A simple mechanism to authenticate via PLAIN
58pub struct PlainAuthenticator {
59    username: String,
60    password: String,
61}
62
63impl Authenticator for PlainAuthenticator {
64    fn mechanism(&self) -> &str {
65        "PLAIN"
66    }
67
68    fn auth(&self) -> Option<String> {
69        Some(format!("\x00{}\x00{}", self.username, self.password))
70    }
71}
72
73impl PlainAuthenticator {
74    pub fn new<U: Into<String>, P: Into<String>>(username: U, password: P) -> Self {
75        Self {
76            username: username.into(),
77            password: password.into(),
78        }
79    }
80}
81
82/// A simple mechanism to authenticate via OAuth2
83pub struct OAuth2Authenticator {
84    user: String,
85    access_token: String,
86}
87
88impl OAuth2Authenticator {
89    pub fn new<U: Into<String>, A: Into<String>>(user: U, access_token: A) -> Self {
90        Self {
91            user: user.into(),
92            access_token: access_token.into(),
93        }
94    }
95}
96
97#[async_trait]
98impl Authenticator for OAuth2Authenticator {
99    fn mechanism(&self) -> &str {
100        "XOAUTH2"
101    }
102
103    fn auth(&self) -> Option<String> {
104        let secret = format!(
105            "user={}\x01auth=Bearer {}\x01\x01",
106            self.user, self.access_token
107        );
108
109        Some(secret)
110    }
111}
112
113#[async_trait]
114pub trait Authenticator {
115    /// The name of the mechanism, e.g: "XOAUTH2" or "KERBEROS_4".
116    fn mechanism(&self) -> &str;
117
118    /// If provided, the return string will be added as an argument to the initial "AUTH" command.
119    ///
120    /// Will automatically be base64 encoded.
121    fn auth(&self) -> Option<String> {
122        None
123    }
124
125    /// Handle a handshake conversation between the server and the client.
126    ///
127    /// The [Communicator] allows you to send and receive data needed for authentication
128    async fn handle<'a, S: Read + Write + Unpin + Send>(
129        &self,
130        _communicator: Communicator<'a, S>,
131    ) -> Result<()> {
132        Ok(())
133    }
134}
135
136pub struct Communicator<'a, S: Read + Write + Unpin + Send> {
137    stream: &'a mut PopStream<S>,
138    requests: VecDeque<Request>,
139}
140
141impl<'a, S: Read + Write + Unpin + Send> Communicator<'a, S> {
142    pub fn new(stream: &'a mut PopStream<S>) -> Self {
143        Self {
144            stream,
145            requests: VecDeque::new(),
146        }
147    }
148
149    pub async fn send<A: Into<String>>(&mut self, secret: A) -> Result<()> {
150        let request: Request = Command::Base64(secret.into()).into();
151
152        self.stream.encode(&request).await?;
153
154        self.requests.push_back(request);
155
156        Ok(())
157    }
158
159    pub async fn next_challenge(&mut self) -> Result<Text> {
160        let command: Command = match self.requests.pop_front() {
161            Some(request) => request.into(),
162            None => Command::Base64(String::new()),
163        };
164
165        let response = self.stream.read_response(command).await?;
166
167        match response {
168            Response::Challenge(challenge) => Ok(challenge),
169            _ => err!(
170                ErrorKind::UnexpectedResponse,
171                "Did not get a challenge as a response"
172            ),
173        }
174    }
175
176    pub async fn stop(&mut self) -> Result<()> {
177        self.stream.send_bytes("*").await
178    }
179}