async_smtp/
smtp_client.rs1use std::fmt::Debug;
2
3use log::{debug, info};
4
5use crate::authentication::{Credentials, Mechanism};
6use crate::commands::*;
7use crate::error::{Error, SmtpResult};
8use crate::extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo};
9use crate::stream::SmtpStream;
10use crate::SendableEmail;
11
12#[cfg(feature = "runtime-async-std")]
13use async_std::io::{BufRead, Write};
14#[cfg(feature = "runtime-tokio")]
15use tokio::io::{AsyncBufRead as BufRead, AsyncWrite as Write};
16
17#[derive(Debug)]
19pub struct SmtpClient {
20 hello_name: ClientId,
22 smtp_utf8: bool,
24 expect_greeting: bool,
28 pipelining: bool,
30}
31
32impl Default for SmtpClient {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl SmtpClient {
40 pub fn new() -> Self {
49 SmtpClient {
50 smtp_utf8: false,
51 hello_name: Default::default(),
52 expect_greeting: true,
53 pipelining: true,
54 }
55 }
56
57 pub fn smtp_utf8(self, enabled: bool) -> SmtpClient {
59 Self {
60 smtp_utf8: enabled,
61 ..self
62 }
63 }
64
65 pub fn pipelining(self, enabled: bool) -> SmtpClient {
67 Self {
68 pipelining: enabled,
69 ..self
70 }
71 }
72
73 pub fn hello_name(self, name: ClientId) -> SmtpClient {
75 Self {
76 hello_name: name,
77 ..self
78 }
79 }
80
81 pub fn without_greeting(self) -> SmtpClient {
85 Self {
86 expect_greeting: false,
87 ..self
88 }
89 }
90}
91
92#[derive(Debug)]
94pub struct SmtpTransport<S: BufRead + Write + Unpin> {
95 server_info: ServerInfo,
97 client_info: SmtpClient,
99 stream: SmtpStream<S>,
101}
102
103impl<S: BufRead + Write + Unpin> SmtpTransport<S> {
104 pub async fn new(builder: SmtpClient, stream: S) -> Result<Self, Error> {
106 let mut stream = SmtpStream::new(stream);
107 if builder.expect_greeting {
108 let _greeting = stream.read_response().await?;
109 }
110 let ehlo_response = stream
111 .ehlo(ClientId::new(builder.hello_name.to_string()))
112 .await?;
113 let server_info = ServerInfo::from_response(&ehlo_response)?;
114
115 debug!("server {}", server_info);
117
118 let transport = SmtpTransport {
119 server_info,
120 client_info: builder,
121 stream,
122 };
123 Ok(transport)
124 }
125
126 pub fn get_mut(&mut self) -> &mut SmtpStream<S> {
128 &mut self.stream
129 }
130
131 pub fn get_ref(&mut self) -> &SmtpStream<S> {
133 &self.stream
134 }
135
136 pub fn into_inner(self) -> SmtpStream<S> {
138 self.stream
139 }
140
141 pub async fn try_login(
143 &mut self,
144 credentials: &Credentials,
145 accepted_mechanisms: &[Mechanism],
146 ) -> Result<(), Error> {
147 if let Some(mechanism) = accepted_mechanisms
148 .iter()
149 .find(|mechanism| self.server_info.supports_auth_mechanism(**mechanism))
150 {
151 self.auth(*mechanism, credentials).await?;
152 } else {
153 info!("No supported authentication mechanisms available");
154 }
155
156 Ok(())
157 }
158
159 pub async fn starttls(mut self) -> Result<S, Error> {
163 if !self.supports_feature(Extension::StartTls) {
164 return Err(From::from("server does not support STARTTLS"));
165 }
166
167 self.stream.command(StarttlsCommand).await?;
168
169 Ok(self.stream.into_inner())
171 }
172
173 fn supports_feature(&self, keyword: Extension) -> bool {
174 self.server_info.supports_feature(keyword)
175 }
176
177 pub async fn quit(&mut self) -> Result<(), Error> {
179 self.stream.command(QuitCommand).await?;
180
181 Ok(())
182 }
183
184 pub async fn auth(&mut self, mechanism: Mechanism, credentials: &Credentials) -> SmtpResult {
186 let mut challenges = 10;
188 let mut response = self
189 .stream
190 .command(AuthCommand::new(mechanism, credentials.clone(), None)?)
191 .await?;
192
193 while challenges > 0 && response.has_code(334) {
194 challenges -= 1;
195 response = self
196 .stream
197 .command(AuthCommand::new_from_response(
198 mechanism,
199 credentials.clone(),
200 &response,
201 )?)
202 .await?;
203 }
204
205 if challenges == 0 {
206 Err(Error::ResponseParsing("Unexpected number of challenges"))
207 } else {
208 Ok(response)
209 }
210 }
211
212 pub async fn send(&mut self, email: SendableEmail) -> SmtpResult {
214 let mut mail_options = vec![];
216
217 if self.supports_feature(Extension::EightBitMime) {
218 mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime));
219 }
220
221 if self.supports_feature(Extension::SmtpUtfEight) && self.client_info.smtp_utf8 {
222 mail_options.push(MailParameter::SmtpUtfEight);
223 }
224
225 let pipelining =
226 self.supports_feature(Extension::Pipelining) && self.client_info.pipelining;
227
228 if pipelining {
229 self.stream
230 .send_command(MailCommand::new(
231 email.envelope().from().cloned(),
232 mail_options,
233 ))
234 .await?;
235 let mut sent_commands = 1;
236
237 for to_address in email.envelope().to() {
239 self.stream
240 .send_command(RcptCommand::new(to_address.clone(), vec![]))
241 .await?;
242 sent_commands += 1;
243 }
244
245 self.stream.send_command(DataCommand).await?;
247 sent_commands += 1;
248
249 for _ in 0..sent_commands {
250 self.stream.read_response().await?;
251 }
252 } else {
253 self.stream
254 .command(MailCommand::new(
255 email.envelope().from().cloned(),
256 mail_options,
257 ))
258 .await?;
259
260 for to_address in email.envelope().to() {
262 self.stream
263 .command(RcptCommand::new(to_address.clone(), vec![]))
264 .await?;
265 debug!("to=<{}>", to_address);
267 }
268
269 self.stream.command(DataCommand).await?;
271 }
272
273 let res = self.stream.message(email.message()).await;
274
275 if let Ok(result) = &res {
277 debug!(
279 "status=sent ({})",
280 result.message.first().unwrap_or(&"no response".to_string())
281 );
282 }
283
284 res
285 }
286}