mail_send_fork/smtp/
builder.rs1use crate::{Credentials, SmtpClient, SmtpClientBuilder};
12use smtp_proto::{EhloResponse, EXT_START_TLS};
13use std::hash::Hash;
14use std::net::IpAddr;
15use std::time::Duration;
16use tokio::{
17 io::{AsyncRead, AsyncWrite},
18 net::TcpStream,
19};
20use tokio_rustls::client::TlsStream;
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::_new(hostname, None, port)
27 }
28
29 pub fn new_bind_ip(hostname: T, addr: IpAddr, port: u16) -> Self {
30 SmtpClientBuilder::_new(hostname, Some(addr), port)
31 }
32
33 fn _new(hostname: T, addr: Option<IpAddr>, port: u16) -> Self {
34 SmtpClientBuilder {
35 addr,
36 port,
37 timeout: Duration::from_secs(60 * 60),
38 tls_connector: build_tls_connector(false),
39 tls_hostname: hostname,
40 tls_implicit: true,
41 is_lmtp: false,
42 local_host: gethostname::gethostname()
43 .to_str()
44 .unwrap_or("[127.0.0.1]")
45 .to_string(),
46 credentials: None,
47 say_ehlo: true,
48 }
49 }
50
51 pub fn allow_invalid_certs(mut self) -> Self {
53 self.tls_connector = build_tls_connector(true);
54 self
55 }
56
57 pub fn implicit_tls(mut self, tls_implicit: bool) -> Self {
59 self.tls_implicit = tls_implicit;
60 self
61 }
62
63 pub fn lmtp(mut self, is_lmtp: bool) -> Self {
65 self.is_lmtp = is_lmtp;
66 self
67 }
68
69 pub fn say_ehlo(mut self, say_ehlo: bool) -> Self {
71 self.say_ehlo = say_ehlo;
72 self
73 }
74
75 pub fn helo_host(mut self, host: impl Into<String>) -> Self {
77 self.local_host = host.into();
78 self
79 }
80
81 pub fn credentials(mut self, credentials: impl Into<Credentials<T>>) -> Self {
83 self.credentials = Some(credentials.into());
84 self
85 }
86
87 pub fn timeout(mut self, timeout: Duration) -> Self {
89 self.timeout = timeout;
90 self
91 }
92
93 pub async fn connect(&self) -> crate::Result<SmtpClient<TlsStream<TcpStream>>> {
95 tokio::time::timeout(self.timeout, async {
96 let mut client = SmtpClient {
97 stream: self.tcp_connect().await?,
98 timeout: self.timeout,
99 };
100
101 let mut client = if self.tls_implicit {
102 let mut client = client
103 .into_tls(&self.tls_connector, self.tls_hostname.as_ref())
104 .await?;
105 client.read().await?.assert_positive_completion()?;
107 client
108 } else {
109 client.read().await?.assert_positive_completion()?;
111
112 let response = if !self.is_lmtp {
114 client.ehlo(&self.local_host).await?
115 } else {
116 client.lhlo(&self.local_host).await?
117 };
118 if response.has_capability(EXT_START_TLS) {
119 client
120 .start_tls(&self.tls_connector, self.tls_hostname.as_ref())
121 .await?
122 } else {
123 return Err(crate::Error::MissingStartTls);
124 }
125 };
126
127 if self.say_ehlo {
128 let capabilities = client.capabilities(&self.local_host, self.is_lmtp).await?;
130 if let Some(credentials) = &self.credentials {
132 client.authenticate(&credentials, &capabilities).await?;
133 }
134 }
135
136 Ok(client)
137 })
138 .await
139 .map_err(|_| crate::Error::Timeout)?
140 }
141
142 pub async fn tcp_connect(&self) -> std::io::Result<TcpStream> {
143 let port = self.port;
144 if let Some(addr) = self.addr {
145 TcpStream::connect((addr, port)).await
146 } else {
147 TcpStream::connect((self.tls_hostname.as_ref(), port)).await
148 }
149 }
150
151 pub async fn connect_plain(&self) -> crate::Result<SmtpClient<TcpStream>> {
153 let mut client = SmtpClient {
154 stream: tokio::time::timeout(self.timeout, async { self.tcp_connect().await })
155 .await
156 .map_err(|_| crate::Error::Timeout)??,
157 timeout: self.timeout,
158 };
159
160 client.read().await?.assert_positive_completion()?;
162
163 if self.say_ehlo {
164 let capabilities = client.capabilities(&self.local_host, self.is_lmtp).await?;
166 if let Some(credentials) = &self.credentials {
168 client.authenticate(&credentials, &capabilities).await?;
169 }
170 }
171
172 Ok(client)
173 }
174}
175
176impl<T: AsyncRead + AsyncWrite + Unpin> SmtpClient<T> {
177 pub async fn capabilities(
178 &mut self,
179 local_host: &str,
180 is_lmtp: bool,
181 ) -> crate::Result<EhloResponse<String>> {
182 if !is_lmtp {
183 self.ehlo(local_host).await
184 } else {
185 self.lhlo(local_host).await
186 }
187 }
188}