1use std::fmt::{self, Display, Formatter};
4
5use crate::{
6 address::Address,
7 transport::smtp::{
8 authentication::{Credentials, Mechanism},
9 error::{self, Error},
10 extension::{ClientId, MailParameter, RcptParameter},
11 response::Response,
12 },
13};
14
15#[derive(PartialEq, Eq, Clone, Debug)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub struct Ehlo {
19 client_id: ClientId,
20}
21
22impl Display for Ehlo {
23 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
24 write!(f, "EHLO {}\r\n", self.client_id)
25 }
26}
27
28impl Ehlo {
29 pub fn new(client_id: ClientId) -> Ehlo {
31 Ehlo { client_id }
32 }
33}
34
35#[derive(PartialEq, Eq, Clone, Debug, Copy)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct Starttls;
39
40impl Display for Starttls {
41 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
42 f.write_str("STARTTLS\r\n")
43 }
44}
45
46#[derive(PartialEq, Eq, Clone, Debug)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49pub struct Mail {
50 sender: Option<Address>,
51 parameters: Vec<MailParameter>,
52}
53
54impl Display for Mail {
55 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
56 write!(
57 f,
58 "MAIL FROM:<{}>",
59 self.sender.as_ref().map_or("", |s| s.as_ref())
60 )?;
61 for parameter in &self.parameters {
62 write!(f, " {parameter}")?;
63 }
64 f.write_str("\r\n")
65 }
66}
67
68impl Mail {
69 pub fn new(sender: Option<Address>, parameters: Vec<MailParameter>) -> Mail {
71 Mail { sender, parameters }
72 }
73}
74
75#[derive(PartialEq, Eq, Clone, Debug)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
78pub struct Rcpt {
79 recipient: Address,
80 parameters: Vec<RcptParameter>,
81}
82
83impl Display for Rcpt {
84 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
85 write!(f, "RCPT TO:<{}>", self.recipient)?;
86 for parameter in &self.parameters {
87 write!(f, " {parameter}")?;
88 }
89 f.write_str("\r\n")
90 }
91}
92
93impl Rcpt {
94 pub fn new(recipient: Address, parameters: Vec<RcptParameter>) -> Rcpt {
96 Rcpt {
97 recipient,
98 parameters,
99 }
100 }
101}
102
103#[derive(PartialEq, Eq, Clone, Debug, Copy)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub struct Data;
107
108impl Display for Data {
109 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
110 f.write_str("DATA\r\n")
111 }
112}
113
114#[derive(PartialEq, Eq, Clone, Debug, Copy)]
116#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
117pub struct Quit;
118
119impl Display for Quit {
120 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
121 f.write_str("QUIT\r\n")
122 }
123}
124
125#[derive(PartialEq, Eq, Clone, Debug, Copy)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128pub struct Noop;
129
130impl Display for Noop {
131 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
132 f.write_str("NOOP\r\n")
133 }
134}
135
136#[derive(PartialEq, Eq, Clone, Debug)]
138#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
139pub struct Help {
140 argument: Option<String>,
141}
142
143impl Display for Help {
144 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
145 f.write_str("HELP")?;
146 if let Some(argument) = &self.argument {
147 write!(f, " {argument}")?;
148 }
149 f.write_str("\r\n")
150 }
151}
152
153impl Help {
154 pub fn new(argument: Option<String>) -> Help {
156 Help { argument }
157 }
158}
159
160#[derive(PartialEq, Eq, Clone, Debug)]
162#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
163pub struct Vrfy {
164 argument: String,
165}
166
167impl Display for Vrfy {
168 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
169 write!(f, "VRFY {}\r\n", self.argument)
170 }
171}
172
173impl Vrfy {
174 pub fn new(argument: String) -> Vrfy {
176 Vrfy { argument }
177 }
178}
179
180#[derive(PartialEq, Eq, Clone, Debug)]
182#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
183pub struct Expn {
184 argument: String,
185}
186
187impl Display for Expn {
188 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
189 write!(f, "EXPN {}\r\n", self.argument)
190 }
191}
192
193impl Expn {
194 pub fn new(argument: String) -> Expn {
196 Expn { argument }
197 }
198}
199
200#[derive(PartialEq, Eq, Clone, Debug, Copy)]
202#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
203pub struct Rset;
204
205impl Display for Rset {
206 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
207 f.write_str("RSET\r\n")
208 }
209}
210
211#[derive(PartialEq, Eq, Clone, Debug)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
214pub struct Auth {
215 mechanism: Mechanism,
216 credentials: Credentials,
217 challenge: Option<String>,
218 response: Option<String>,
219}
220
221impl Display for Auth {
222 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
223 let encoded_response = self.response.as_ref().map(crate::base64::encode);
224
225 if self.mechanism.supports_initial_response() {
226 write!(f, "AUTH {} {}", self.mechanism, encoded_response.unwrap())?;
227 } else {
228 match encoded_response {
229 Some(response) => f.write_str(&response)?,
230 None => write!(f, "AUTH {}", self.mechanism)?,
231 }
232 }
233 f.write_str("\r\n")
234 }
235}
236
237impl Auth {
238 pub fn new(
240 mechanism: Mechanism,
241 credentials: Credentials,
242 challenge: Option<String>,
243 ) -> Result<Auth, Error> {
244 let response = if mechanism.supports_initial_response() || challenge.is_some() {
245 Some(mechanism.response(&credentials, challenge.as_deref())?)
246 } else {
247 None
248 };
249 Ok(Auth {
250 mechanism,
251 credentials,
252 challenge,
253 response,
254 })
255 }
256
257 pub fn new_from_response(
260 mechanism: Mechanism,
261 credentials: Credentials,
262 response: &Response,
263 ) -> Result<Auth, Error> {
264 if !response.has_code(334) {
265 return Err(error::response("Expecting a challenge"));
266 }
267
268 let encoded_challenge = response
269 .first_word()
270 .ok_or_else(|| error::response("Could not read auth challenge"))?;
271 #[cfg(feature = "tracing")]
272 tracing::debug!("auth encoded challenge: {}", encoded_challenge);
273
274 let decoded_base64 = crate::base64::decode(encoded_challenge).map_err(error::response)?;
275 let decoded_challenge = String::from_utf8(decoded_base64).map_err(error::response)?;
276 #[cfg(feature = "tracing")]
277 tracing::debug!("auth decoded challenge: {}", decoded_challenge);
278
279 let response = Some(mechanism.response(&credentials, Some(decoded_challenge.as_ref()))?);
280
281 Ok(Auth {
282 mechanism,
283 credentials,
284 challenge: Some(decoded_challenge),
285 response,
286 })
287 }
288}
289
290#[cfg(test)]
291mod test {
292 use std::str::FromStr;
293
294 use super::*;
295 use crate::transport::smtp::extension::MailBodyParameter;
296
297 #[test]
298 fn test_display() {
299 let id = ClientId::Domain("localhost".to_owned());
300 let email = Address::from_str("test@example.com").unwrap();
301 let mail_parameter = MailParameter::Other {
302 keyword: "TEST".to_owned(),
303 value: Some("value".to_owned()),
304 };
305 let rcpt_parameter = RcptParameter::Other {
306 keyword: "TEST".to_owned(),
307 value: Some("value".to_owned()),
308 };
309 assert_eq!(format!("{}", Ehlo::new(id)), "EHLO localhost\r\n");
310 assert_eq!(
311 format!("{}", Mail::new(Some(email.clone()), vec![])),
312 "MAIL FROM:<test@example.com>\r\n"
313 );
314 assert_eq!(format!("{}", Mail::new(None, vec![])), "MAIL FROM:<>\r\n");
315 assert_eq!(
316 format!(
317 "{}",
318 Mail::new(Some(email.clone()), vec![MailParameter::Size(42)])
319 ),
320 "MAIL FROM:<test@example.com> SIZE=42\r\n"
321 );
322 assert_eq!(
323 format!(
324 "{}",
325 Mail::new(
326 Some(email.clone()),
327 vec![
328 MailParameter::Size(42),
329 MailParameter::Body(MailBodyParameter::EightBitMime),
330 mail_parameter,
331 ],
332 )
333 ),
334 "MAIL FROM:<test@example.com> SIZE=42 BODY=8BITMIME TEST=value\r\n"
335 );
336 assert_eq!(
337 format!("{}", Rcpt::new(email.clone(), vec![])),
338 "RCPT TO:<test@example.com>\r\n"
339 );
340 assert_eq!(
341 format!("{}", Rcpt::new(email, vec![rcpt_parameter])),
342 "RCPT TO:<test@example.com> TEST=value\r\n"
343 );
344 assert_eq!(format!("{Quit}"), "QUIT\r\n");
345 assert_eq!(format!("{Data}"), "DATA\r\n");
346 assert_eq!(format!("{Noop}"), "NOOP\r\n");
347 assert_eq!(format!("{}", Help::new(None)), "HELP\r\n");
348 assert_eq!(
349 format!("{}", Help::new(Some("test".to_owned()))),
350 "HELP test\r\n"
351 );
352 assert_eq!(format!("{}", Vrfy::new("test".to_owned())), "VRFY test\r\n");
353 assert_eq!(format!("{}", Expn::new("test".to_owned())), "EXPN test\r\n");
354 assert_eq!(format!("{Rset}"), "RSET\r\n");
355 let credentials = Credentials::new("user".to_owned(), "password".to_owned());
356 assert_eq!(
357 format!(
358 "{}",
359 Auth::new(Mechanism::Plain, credentials.clone(), None).unwrap()
360 ),
361 "AUTH PLAIN AHVzZXIAcGFzc3dvcmQ=\r\n"
362 );
363 assert_eq!(
364 format!(
365 "{}",
366 Auth::new(Mechanism::Login, credentials, None).unwrap()
367 ),
368 "AUTH LOGIN\r\n"
369 );
370 }
371}