Skip to main content

new_tokio_smtp/command/auth/
plain.rs

1use std::error::Error as ErrorTrait;
2use std::fmt::{self, Display};
3use std::sync::Arc;
4
5use base64::encode;
6
7use crate::{error::MissingCapabilities, Cmd, EhloData, ExecFuture, Io};
8
9use super::validate_auth_capability;
10
11/// AUTH PLAIN smtp authentication based on rfc4954/rfc4616
12#[derive(Debug, Clone)]
13pub struct Plain {
14    authorization_identity: String,
15    authentication_identity: String,
16    password: String,
17}
18
19impl Plain {
20    /// Create a auth plain command from a given username and password.
21    pub fn from_username<I1, I2>(user: I1, password: I2) -> Result<Self, NullCodePointError>
22    where
23        I1: Into<String> + AsRef<str>,
24        I2: Into<String> + AsRef<str>,
25    {
26        validate_no_null_cps(&user)?;
27        validate_no_null_cps(&password)?;
28
29        let user = user.into();
30        Ok(Plain {
31            authentication_identity: user.clone(),
32            authorization_identity: user,
33            password: password.into(),
34        })
35    }
36
37    /// Create a auth plain command from a authorization identity a authentication identity and a password.
38    ///
39    /// Most times authorization and authentication identities are the same (and happen to be
40    /// the username) in which case `auth::Plain::from_username` can be used.
41    pub fn new<I1, I2, I3>(
42        authorization_identity: I1,
43        authentication_identity: I2,
44        password: I3,
45    ) -> Result<Self, NullCodePointError>
46    where
47        I1: Into<String> + AsRef<str>,
48        I2: Into<String> + AsRef<str>,
49        I3: Into<String> + AsRef<str>,
50    {
51        validate_no_null_cps(&authorization_identity)?;
52        validate_no_null_cps(&authentication_identity)?;
53        validate_no_null_cps(&password)?;
54
55        Ok(Plain {
56            authentication_identity: authentication_identity.into(),
57            authorization_identity: authorization_identity.into(),
58            password: password.into(),
59        })
60    }
61
62    /// Returns the authorization identity which will be used.
63    pub fn authorization_identity(&self) -> &str {
64        &self.authorization_identity
65    }
66
67    /// Returns the authentication identity which will be used.
68    pub fn authentication_identity(&self) -> &str {
69        &self.authentication_identity
70    }
71
72    //intentionally no fn password(&self)!
73
74    fn exec_ref(&self, io: Io) -> ExecFuture {
75        let auth_str = encode(&format!(
76            "{}\0{}\0{}",
77            &self.authorization_identity, &self.authentication_identity, &self.password
78        ));
79
80        io.exec_simple_cmd(&["AUTH PLAIN ", auth_str.as_str()])
81    }
82}
83
84impl Cmd for Plain {
85    fn check_cmd_availability(&self, caps: Option<&EhloData>) -> Result<(), MissingCapabilities> {
86        validate_auth_capability(caps, "PLAIN")
87    }
88
89    fn exec(self, con: Io) -> ExecFuture {
90        self.exec_ref(con)
91    }
92}
93
94impl Cmd for Arc<Plain> {
95    fn check_cmd_availability(&self, caps: Option<&EhloData>) -> Result<(), MissingCapabilities> {
96        let me: &Plain = &*self;
97        me.check_cmd_availability(caps)
98    }
99
100    fn exec(self, con: Io) -> ExecFuture {
101        self.exec_ref(con)
102    }
103}
104
105fn validate_no_null_cps<R>(inp: R) -> Result<(), NullCodePointError>
106where
107    R: AsRef<str>,
108{
109    for bch in inp.as_ref().bytes() {
110        if bch == b'\0' {
111            return Err(NullCodePointError);
112        }
113    }
114    Ok(())
115}
116
117/// Error returned if by auth plain if identity or password contained a null code point.
118#[derive(Copy, Clone, Debug)]
119pub struct NullCodePointError;
120
121impl Display for NullCodePointError {
122    fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
123        write!(fter, "input (username/password) contained null byte")
124    }
125}
126
127impl ErrorTrait for NullCodePointError {}