mail_send/smtp/
builder.rs1use smtp_proto::{EhloResponse, EXT_START_TLS};
8use std::hash::Hash;
9use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
10use std::time::Duration;
11use tokio::net::TcpSocket;
12use tokio::{
13 io,
14 io::{AsyncRead, AsyncWrite},
15 net::TcpStream,
16};
17use tokio_rustls::client::TlsStream;
18
19use crate::{Credentials, SmtpClient, SmtpClientBuilder};
20
21use super::{tls::build_tls_connector, AssertReply};
22
23impl<T: AsRef<str> + PartialEq + Eq + Hash> SmtpClientBuilder<T> {
24 pub fn new(hostname: T, port: u16) -> Self {
25 SmtpClientBuilder {
26 addr: format!("{}:{}", hostname.as_ref(), port),
27 timeout: Duration::from_secs(60 * 60),
28 tls_connector: build_tls_connector(false),
29 tls_hostname: hostname,
30 tls_implicit: true,
31 is_lmtp: false,
32 local_host: gethostname::gethostname()
33 .to_str()
34 .unwrap_or("[127.0.0.1]")
35 .to_string(),
36 credentials: None,
37 say_ehlo: true,
38 local_ip: None,
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 fn local_ip(mut self, local_ip: IpAddr) -> Self {
92 self.local_ip = Some(local_ip);
93 self
94 }
95
96 async fn tcp_stream(&self) -> io::Result<TcpStream> {
97 if let Some(local_addr) = self.local_ip {
98 let remote_addrs = self.addr.to_socket_addrs()?;
99 let mut last_err = None;
100
101 for addr in remote_addrs {
102 let local_addr = SocketAddr::new(local_addr, 0);
103 let socket = match local_addr.ip() {
104 IpAddr::V4(_) => TcpSocket::new_v4()?,
105 IpAddr::V6(_) => TcpSocket::new_v6()?,
106 };
107 socket.bind(local_addr)?;
108
109 match socket.connect(addr).await {
110 Ok(stream) => return Ok(stream),
111 Err(e) => last_err = Some(e),
112 }
113 }
114
115 Err(last_err.unwrap_or_else(|| {
116 io::Error::new(
117 io::ErrorKind::InvalidInput,
118 "could not resolve to any address",
119 )
120 }))
121 } else {
122 TcpStream::connect(&self.addr).await
123 }
124 }
125
126 pub async fn connect(&self) -> crate::Result<SmtpClient<TlsStream<TcpStream>>> {
128 tokio::time::timeout(self.timeout, async {
129 let mut client = SmtpClient {
130 stream: self.tcp_stream().await?,
131 timeout: self.timeout,
132 };
133
134 let mut client = if self.tls_implicit {
135 let mut client = client
136 .into_tls(&self.tls_connector, self.tls_hostname.as_ref())
137 .await?;
138 client.read().await?.assert_positive_completion()?;
140 client
141 } else {
142 client.read().await?.assert_positive_completion()?;
144
145 let response = if !self.is_lmtp {
147 client.ehlo(&self.local_host).await?
148 } else {
149 client.lhlo(&self.local_host).await?
150 };
151 if response.has_capability(EXT_START_TLS) {
152 client
153 .start_tls(&self.tls_connector, self.tls_hostname.as_ref())
154 .await?
155 } else {
156 return Err(crate::Error::MissingStartTls);
157 }
158 };
159
160 if self.say_ehlo {
161 let capabilities = client.capabilities(&self.local_host, self.is_lmtp).await?;
163 if let Some(credentials) = &self.credentials {
165 client.authenticate(&credentials, &capabilities).await?;
166 }
167 }
168
169 Ok(client)
170 })
171 .await
172 .map_err(|_| crate::Error::Timeout)?
173 }
174
175 pub async fn connect_plain(&self) -> crate::Result<SmtpClient<TcpStream>> {
177 let mut client = SmtpClient {
178 stream: tokio::time::timeout(self.timeout, async { self.tcp_stream().await })
179 .await
180 .map_err(|_| crate::Error::Timeout)??,
181 timeout: self.timeout,
182 };
183
184 client.read().await?.assert_positive_completion()?;
186
187 if self.say_ehlo {
188 let capabilities = client.capabilities(&self.local_host, self.is_lmtp).await?;
190 if let Some(credentials) = &self.credentials {
192 client.authenticate(&credentials, &capabilities).await?;
193 }
194 }
195
196 Ok(client)
197 }
198}
199
200impl<T: AsyncRead + AsyncWrite + Unpin> SmtpClient<T> {
201 pub async fn capabilities(
202 &mut self,
203 local_host: &str,
204 is_lmtp: bool,
205 ) -> crate::Result<EhloResponse<String>> {
206 if !is_lmtp {
207 self.ehlo(local_host).await
208 } else {
209 self.lhlo(local_host).await
210 }
211 }
212}