1use crate::sasl;
2
3use anyhow::{anyhow, bail, Result};
4
5const PLAIN: &str = "PLAIN";
7
8pub 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
40pub type PlainAuthenticator = Box<dyn Fn(&str, &str, &str) -> Result<()>>;
45
46pub 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 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}