1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/*!
# Sasl

This module provides support for SASL (Simple Authentication and Security Layer), as specified in [RFC4422](https://datatracker.ietf.org/doc/html/rfc4422)

It allows one to use these mechanisms to authenticate with a Pop3 compatible server and implement more mechanisms if they are needed.

The mechanisms for PLAIN and XOAUTH2 are already present as they are commonly used.

Implementing a mechanism is simple:

```rust,ignore
pub struct MyAuthenticator {
    username: String,
    secret_token: String,
}

impl Authenticator for MyAuthenticator {
    fn mechanism(&self) -> &str {
        "SUPER_COOL_MECHANISM"
    }

    fn auth(&self) -> Option<String> {
        // Specify your cool format
        Some(format!("\x00{}\x00{}", self.username, self.secret_token))
    }

    async fn handle<'a, S: Read + Write + Unpin + Send>(
        &self,
        communicator: Communicator<'a, S>,
    ) -> Result<()> {
        let challenge = communicator.next_challenge().await?;

        let response = mechanism_lib::handle_challenge(challenge)?;

        communicator.send(response).await?;

        Ok(())
    }
}
```
*/

use std::collections::VecDeque;

use async_trait::async_trait;

use crate::{
    command::Command,
    error::{err, ErrorKind, Result},
    request::Request,
    response::{types::message::Text, Response},
    runtime::io::{Read, Write},
    stream::PopStream,
};

/// A simple mechanism to authenticate via PLAIN
pub struct PlainAuthenticator {
    username: String,
    password: String,
}

impl Authenticator for PlainAuthenticator {
    fn mechanism(&self) -> &str {
        "PLAIN"
    }

    fn auth(&self) -> Option<String> {
        Some(format!("\x00{}\x00{}", self.username, self.password))
    }
}

impl PlainAuthenticator {
    pub fn new<U: Into<String>, P: Into<String>>(username: U, password: P) -> Self {
        Self {
            username: username.into(),
            password: password.into(),
        }
    }
}

/// A simple mechanism to authenticate via OAuth2
pub struct OAuth2Authenticator {
    user: String,
    access_token: String,
}

impl OAuth2Authenticator {
    pub fn new<U: Into<String>, A: Into<String>>(user: U, access_token: A) -> Self {
        Self {
            user: user.into(),
            access_token: access_token.into(),
        }
    }
}

#[async_trait]
impl Authenticator for OAuth2Authenticator {
    fn mechanism(&self) -> &str {
        "XOAUTH2"
    }

    fn auth(&self) -> Option<String> {
        let secret = format!(
            "user={}\x01auth=Bearer {}\x01\x01",
            self.user, self.access_token
        );

        Some(secret)
    }
}

#[async_trait]
pub trait Authenticator {
    /// The name of the mechanism, e.g: "XOAUTH2" or "KERBEROS_4".
    fn mechanism(&self) -> &str;

    /// If provided, the return string will be added as an argument to the initial "AUTH" command.
    ///
    /// Will automatically be base64 encoded.
    fn auth(&self) -> Option<String> {
        None
    }

    /// Handle a handshake conversation between the server and the client.
    ///
    /// The [Communicator] allows you to send and receive data needed for authentication
    async fn handle<'a, S: Read + Write + Unpin + Send>(
        &self,
        _communicator: Communicator<'a, S>,
    ) -> Result<()> {
        Ok(())
    }
}

pub struct Communicator<'a, S: Read + Write + Unpin + Send> {
    stream: &'a mut PopStream<S>,
    requests: VecDeque<Request>,
}

impl<'a, S: Read + Write + Unpin + Send> Communicator<'a, S> {
    pub fn new(stream: &'a mut PopStream<S>) -> Self {
        Self {
            stream,
            requests: VecDeque::new(),
        }
    }

    pub async fn send<A: Into<String>>(&mut self, secret: A) -> Result<()> {
        let request: Request = Command::Base64(secret.into()).into();

        self.stream.encode(&request).await?;

        self.requests.push_back(request);

        Ok(())
    }

    pub async fn next_challenge(&mut self) -> Result<Text> {
        let command: Command = match self.requests.pop_front() {
            Some(request) => request.into(),
            None => Command::Base64(String::new()),
        };

        let response = self.stream.read_response(command).await?;

        match response {
            Response::Challenge(challenge) => Ok(challenge),
            _ => err!(
                ErrorKind::UnexpectedResponse,
                "Did not get a challenge as a response"
            ),
        }
    }

    pub async fn stop(&mut self) -> Result<()> {
        self.stream.send_bytes("*").await
    }
}