lib_rust_sasl/
plain.rs

1use crate::sasl;
2
3use anyhow::{anyhow, bail, Result};
4
5/// The PLAIN mechanism name.
6const PLAIN: &str = "PLAIN";
7
8/// A client implementation of the PLAIN authentication mechanism, as described
9/// in RFC 4616. Authorization identity may be left blank to indicate that it is
10/// the same as the username.
11pub struct PlainClient {
12    identity: String,
13    username: String,
14    password: String,
15}
16
17impl PlainClient {
18    pub fn new(identity: String, username: String, password: String) -> Self {
19        Self {
20            identity,
21            username,
22            password,
23        }
24    }
25}
26
27impl sasl::Client for PlainClient {
28    fn start(&mut self) -> Result<(String, Vec<u8>)> {
29        Ok((
30            PLAIN.to_string(),
31            format!("{}\x00{}\x00{}", self.identity, self.username, self.password).into_bytes()
32        ))
33    }
34
35    fn next(&mut self, _challenge: &[u8]) -> Result<Vec<u8>> {
36        Err(anyhow!(sasl::ERR_UNEXPECTED_SERVER_CHALLENGE))
37    }
38}
39
40/// authenticates users with an identity, a username and a password. If the
41/// identity is left blank, it indicates that it is the same as the username.
42/// If identity is not empty and the server doesn't support it, an error must be
43/// returned.
44pub type PlainAuthenticator = Box<dyn Fn(&str, &str, &str) -> Result<()>>;
45
46/// A server implementation of the PLAIN authentication mechanism, as described
47/// in RFC 4616.
48pub struct PlainServer {
49    done: bool,
50    authenticator: PlainAuthenticator,
51}
52
53impl PlainServer {
54    pub fn new<F>(authenticator: F) -> Self
55    where F: Fn(&str, &str, &str) -> Result<()> + 'static {
56        Self {
57            done: false,
58            authenticator: Box::new(authenticator),
59        }
60    }
61}
62
63impl sasl::Server for PlainServer {
64    fn next(&mut self, response: Option<&[u8]>) -> Result<(Vec<u8>, bool)> {
65        if self.done {
66            bail!(sasl::ERR_UNEXPECTED_CLIENT_RESPONSE);
67        }
68
69        // No initial response, send an empty challenge
70        if response.is_none() {
71            return Ok((Vec::new(), false));
72        }
73        let response = response.unwrap();
74
75        let mut parts = response.split(|&b| b == b'\x00');
76        let identity = parts.next().ok_or_else(|| anyhow!("sasl: missing identity"))?;
77        let username = parts.next().ok_or_else(|| anyhow!("sasl: missing username"))?;
78        let password = parts.next().ok_or_else(|| anyhow!("sasl: missing password"))?;
79
80        (self.authenticator)(
81            std::str::from_utf8(identity)?,
82            std::str::from_utf8(username)?,
83            std::str::from_utf8(password)?,
84        )?;
85
86        self.done = true;
87
88        Ok((Vec::new(), true))
89    }
90}
91
92#[test]
93fn test_new_plain_client() -> Result<()> {
94    use crate::sasl::Client;
95
96    let mut c = PlainClient::new("identity".to_string(), "username".to_string(), "password".to_string());
97
98    let (mech, ir) = c.start().map_err(|e| anyhow!("Error while starting client: {}", e))?;
99    if mech != PLAIN {
100        bail!("Invalid mechanism name: {}", mech);
101    }
102
103    let expected = vec!(105, 100, 101, 110, 116, 105, 116, 121, 0, 117, 115, 101, 114, 110, 97, 109, 101, 0, 112, 97, 115, 115, 119, 111, 114, 100);
104    if ir != expected {
105        bail!("Invalid initial response: {:?}", ir);
106    }
107
108    Ok(())
109}
110
111#[test]
112fn test_new_plain_server() -> Result<()> {
113    use crate::sasl::Server;
114
115    let mut authenticated = false;
116    let unsafe_ref_authenticated = &mut authenticated as *mut bool;
117
118    let mut s = PlainServer::new(move |identity, username, password| {
119        if username != "username" {
120            bail!("Invalid username: {}", username);
121        }
122        if password != "password" {
123            bail!("Invalid password: {}", password);
124        }
125        if identity != "identity" {
126            bail!("Invalid identity: {}", identity);
127        }
128
129        unsafe {
130            unsafe_ref_authenticated.write(true);
131        }
132        Ok(())
133    });
134
135    let (challenge, done) = s.next(None).map_err(|e| anyhow!("Error while starting server: {}", e))?;
136    if done {
137        bail!("Done after starting server");
138    }
139    if !challenge.is_empty() {
140        bail!("Invalid non-empty initial challenge: {:?}", challenge);
141    }
142
143    let response = vec!(105, 100, 101, 110, 116, 105, 116, 121, 0, 117, 115, 101, 114, 110, 97, 109, 101, 0, 112, 97, 115, 115, 119, 111, 114, 100);
144    let (challenge, done) = s.next(Some(&response)).map_err(|e| anyhow!("Error while finishing authentication:: {}", e))?;
145    if !done {
146        bail!("Authentication not finished after sending PLAIN credentials");
147    }
148    if !challenge.is_empty() {
149        bail!("Invalid non-empty final challenge: {:?}", challenge);
150    }
151
152    if !authenticated {
153        bail!("Authentication failed");
154    }
155
156    Ok(())
157}