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