mail_send/smtp/
builder.rs1use smtp_proto::{EhloResponse, EXT_START_TLS};
12use std::hash::Hash;
13use std::time::Duration;
14use tokio::{
15 io::{AsyncRead, AsyncWrite},
16 net::TcpStream,
17};
18use tokio_rustls::client::TlsStream;
19
20use crate::{Credentials, SmtpClient, SmtpClientBuilder};
21
22use super::{tls::build_tls_connector, AssertReply};
23
24impl<T: AsRef<str> + PartialEq + Eq + Hash> SmtpClientBuilder<T> {
25 pub fn new(hostname: T, port: u16) -> Self {
26 SmtpClientBuilder {
27 addr: format!("{}:{}", hostname.as_ref(), port),
28 timeout: Duration::from_secs(60 * 60),
29 tls_connector: build_tls_connector(false),
30 tls_hostname: hostname,
31 tls_implicit: true,
32 is_lmtp: false,
33 local_host: gethostname::gethostname()
34 .to_str()
35 .unwrap_or("[127.0.0.1]")
36 .to_string(),
37 credentials: None,
38 say_ehlo: true,
39 }
40 }
41
42 pub fn allow_invalid_certs(mut self) -> Self {
44 self.tls_connector = build_tls_connector(true);
45 self
46 }
47
48 pub fn implicit_tls(mut self, tls_implicit: bool) -> Self {
50 self.tls_implicit = tls_implicit;
51 self
52 }
53
54 pub fn lmtp(mut self, is_lmtp: bool) -> Self {
56 self.is_lmtp = is_lmtp;
57 self
58 }
59
60 pub fn say_ehlo(mut self, say_ehlo: bool) -> Self {
62 self.say_ehlo = say_ehlo;
63 self
64 }
65
66 pub fn helo_host(mut self, host: impl Into<String>) -> Self {
68 self.local_host = host.into();
69 self
70 }
71
72 pub fn credentials(mut self, credentials: impl Into<Credentials<T>>) -> Self {
74 self.credentials = Some(credentials.into());
75 self
76 }
77
78 pub fn timeout(mut self, timeout: Duration) -> Self {
80 self.timeout = timeout;
81 self
82 }
83
84 pub async fn connect(&self) -> crate::Result<SmtpClient<TlsStream<TcpStream>>> {
86 tokio::time::timeout(self.timeout, async {
87 let mut client = SmtpClient {
88 stream: TcpStream::connect(&self.addr).await?,
89 timeout: self.timeout,
90 };
91
92 let mut client = if self.tls_implicit {
93 let mut client = client
94 .into_tls(&self.tls_connector, self.tls_hostname.as_ref())
95 .await?;
96 client.read().await?.assert_positive_completion()?;
98 client
99 } else {
100 client.read().await?.assert_positive_completion()?;
102
103 let response = if !self.is_lmtp {
105 client.ehlo(&self.local_host).await?
106 } else {
107 client.lhlo(&self.local_host).await?
108 };
109 if response.has_capability(EXT_START_TLS) {
110 client
111 .start_tls(&self.tls_connector, self.tls_hostname.as_ref())
112 .await?
113 } else {
114 return Err(crate::Error::MissingStartTls);
115 }
116 };
117
118 if self.say_ehlo {
119 let capabilities = client.capabilities(&self.local_host, self.is_lmtp).await?;
121 if let Some(credentials) = &self.credentials {
123 client.authenticate(&credentials, &capabilities).await?;
124 }
125 }
126
127 Ok(client)
128 })
129 .await
130 .map_err(|_| crate::Error::Timeout)?
131 }
132
133 pub async fn connect_plain(&self) -> crate::Result<SmtpClient<TcpStream>> {
135 let mut client = SmtpClient {
136 stream: tokio::time::timeout(self.timeout, async {
137 TcpStream::connect(&self.addr).await
138 })
139 .await
140 .map_err(|_| crate::Error::Timeout)??,
141 timeout: self.timeout,
142 };
143
144 client.read().await?.assert_positive_completion()?;
146
147 if self.say_ehlo {
148 let capabilities = client.capabilities(&self.local_host, self.is_lmtp).await?;
150 if let Some(credentials) = &self.credentials {
152 client.authenticate(&credentials, &capabilities).await?;
153 }
154 }
155
156 Ok(client)
157 }
158}
159
160impl<T: AsyncRead + AsyncWrite + Unpin> SmtpClient<T> {
161 pub async fn capabilities(
162 &mut self,
163 local_host: &str,
164 is_lmtp: bool,
165 ) -> crate::Result<EhloResponse<String>> {
166 if !is_lmtp {
167 self.ehlo(local_host).await
168 } else {
169 self.lhlo(local_host).await
170 }
171 }
172}