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
use std::fmt::{self, Display};
use std::sync::Arc;
use std::error::{Error as ErrorTrait};

use base64::encode;

use ::{ExecFuture, Cmd, EhloData, Io};
use ::error::MissingCapabilities;

use super::validate_auth_capability;

/// AUTH PLAIN smtp authentication based on rfc4954/rfc4616
#[derive(Debug, Clone)]
pub struct Plain {
    authorization_identity: String,
    authentication_identity: String,
    password: String
}

impl Plain {

    /// Create a auth plain command from a given username and password.
    pub fn from_username<I1, I2>(user: I1, password: I2) -> Result<Self, NullCodePointError>
        where I1: Into<String> + AsRef<str>, I2: Into<String> + AsRef<str>
    {
        validate_no_null_cps(&user)?;
        validate_no_null_cps(&password)?;

        let user = user.into();
        Ok(Plain {
            authentication_identity: user.clone(),
            authorization_identity: user,
            password: password.into()
        })
    }

    /// Create a auth plain command from a authorization identity a authentication identity and a password.
    ///
    /// Most times authorization and authentication identities are the same (and happen to be
    /// the username) in which case `auth::Plain::from_username` can be used.
    pub fn new<I1,I2,I3>(
        authorization_identity: I1,
        authentication_identity: I2,
        password: I3
    ) -> Result<Self, NullCodePointError>
        where I1: Into<String> + AsRef<str>,
              I2: Into<String> + AsRef<str>,
              I3: Into<String> + AsRef<str>
    {
        validate_no_null_cps(&authorization_identity)?;
        validate_no_null_cps(&authentication_identity)?;
        validate_no_null_cps(&password)?;

        Ok(Plain {
            authentication_identity: authentication_identity.into(),
            authorization_identity: authorization_identity.into(),
            password: password.into()
        })
    }

    /// Returns the authorization identity which will be used.
    pub fn authorization_identity(&self) -> &str {
        &self.authorization_identity
    }

    /// Returns the authentication identity which will be used.
    pub fn authentication_identity(&self) -> &str {
        &self.authentication_identity
    }

    //intentionally no fn password(&self)!

    fn exec_ref(&self, io: Io) -> ExecFuture {
        let auth_str = encode(&format!("{}\0{}\0{}",
                               &self.authorization_identity,
                               &self.authentication_identity,
                               &self.password));

        io.exec_simple_cmd(&["AUTH PLAIN ", auth_str.as_str()])
    }
}

impl Cmd for Plain {

    fn check_cmd_availability(&self, caps: Option<&EhloData>)
        -> Result<(), MissingCapabilities>
    {
        validate_auth_capability(caps, "PLAIN")
    }

    fn exec(self, con: Io) -> ExecFuture {
        self.exec_ref(con)
    }
}

impl Cmd for Arc<Plain> {

    fn check_cmd_availability(&self, caps: Option<&EhloData>)
        -> Result<(), MissingCapabilities>
    {
        let me: &Plain = &*self;
        me.check_cmd_availability(caps)
    }

    fn exec(self, con: Io) -> ExecFuture {
        self.exec_ref(con)
    }
}

fn validate_no_null_cps<R>(inp: R) -> Result<(), NullCodePointError>
    where R: AsRef<str>
{
    for bch in inp.as_ref().bytes() {
        if bch == b'\0' {
            return Err(NullCodePointError)
        }
    }
    Ok(())
}

/// Error returned if by auth plain if identity or password contained a null code point.
#[derive(Copy, Clone, Debug)]
pub struct NullCodePointError;

impl Display for NullCodePointError {
    fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
        fter.write_str(self.description())
    }
}

impl ErrorTrait for NullCodePointError {
    fn description(&self) -> &str {
        "input (username/password) contained null byte"
    }

    fn cause(&self) -> Option<&ErrorTrait> {
        None
    }
}