1 #[cfg(feature = "pool")]
2 use std::sync::Arc;
3 use std::time::Duration;
4 
5 #[cfg(feature = "pool")]
6 use super::pool::sync_impl::Pool;
7 #[cfg(feature = "pool")]
8 use super::PoolConfig;
9 use super::{ClientId, Credentials, Error, Mechanism, Response, SmtpConnection, SmtpInfo};
10 #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
11 use super::{Tls, TlsParameters, SUBMISSIONS_PORT, SUBMISSION_PORT};
12 use crate::{address::Envelope, Transport};
13 
14 /// Sends emails using the SMTP protocol
15 #[cfg_attr(docsrs, doc(cfg(feature = "smtp-transport")))]
16 #[derive(Clone)]
17 pub struct SmtpTransport {
18     #[cfg(feature = "pool")]
19     inner: Arc<Pool>,
20     #[cfg(not(feature = "pool"))]
21     inner: SmtpClient,
22 }
23 
24 impl Transport for SmtpTransport {
25     type Ok = Response;
26     type Error = Error;
27 
28     /// Sends an email
send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error>29     fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
30         let mut conn = self.inner.connection()?;
31 
32         let result = conn.send(envelope, email)?;
33 
34         #[cfg(not(feature = "pool"))]
35         conn.quit()?;
36 
37         Ok(result)
38     }
39 }
40 
41 impl SmtpTransport {
42     /// Simple and secure transport, using TLS connections to communicate with the SMTP server
43     ///
44     /// The right option for most SMTP servers.
45     ///
46     /// Creates an encrypted transport over submissions port, using the provided domain
47     /// to validate TLS certificates.
48     #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
49     #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
relay(relay: &str) -> Result<SmtpTransportBuilder, Error>50     pub fn relay(relay: &str) -> Result<SmtpTransportBuilder, Error> {
51         let tls_parameters = TlsParameters::new(relay.into())?;
52 
53         Ok(Self::builder_dangerous(relay)
54             .port(SUBMISSIONS_PORT)
55             .tls(Tls::Wrapper(tls_parameters)))
56     }
57 
58     /// Simple an secure transport, using STARTTLS to obtain encrypted connections
59     ///
60     /// Alternative to [`SmtpTransport::relay`](#method.relay), for SMTP servers
61     /// that don't take SMTPS connections.
62     ///
63     /// Creates an encrypted transport over submissions port, by first connecting using
64     /// an unencrypted connection and then upgrading it with STARTTLS. The provided
65     /// domain is used to validate TLS certificates.
66     ///
67     /// An error is returned if the connection can't be upgraded. No credentials
68     /// or emails will be sent to the server, protecting from downgrade attacks.
69     #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
70     #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
starttls_relay(relay: &str) -> Result<SmtpTransportBuilder, Error>71     pub fn starttls_relay(relay: &str) -> Result<SmtpTransportBuilder, Error> {
72         let tls_parameters = TlsParameters::new(relay.into())?;
73 
74         Ok(Self::builder_dangerous(relay)
75             .port(SUBMISSION_PORT)
76             .tls(Tls::Required(tls_parameters)))
77     }
78 
79     /// Creates a new local SMTP client to port 25
80     ///
81     /// Shortcut for local unencrypted relay (typical local email daemon that will handle relaying)
unencrypted_localhost() -> SmtpTransport82     pub fn unencrypted_localhost() -> SmtpTransport {
83         Self::builder_dangerous("localhost").build()
84     }
85 
86     /// Creates a new SMTP client
87     ///
88     /// Defaults are:
89     ///
90     /// * No authentication
91     /// * No TLS
92     /// * A 60 seconds timeout for smtp commands
93     /// * Port 25
94     ///
95     /// Consider using [`SmtpTransport::relay`](#method.relay) or
96     /// [`SmtpTransport::starttls_relay`](#method.starttls_relay) instead,
97     /// if possible.
builder_dangerous<T: Into<String>>(server: T) -> SmtpTransportBuilder98     pub fn builder_dangerous<T: Into<String>>(server: T) -> SmtpTransportBuilder {
99         let new = SmtpInfo {
100             server: server.into(),
101             ..Default::default()
102         };
103 
104         SmtpTransportBuilder {
105             info: new,
106             #[cfg(feature = "pool")]
107             pool_config: PoolConfig::default(),
108         }
109     }
110 
111     /// Tests the SMTP connection
112     ///
113     /// `test_connection()` tests the connection by using the SMTP NOOP command.
114     /// The connection is closed afterwards if a connection pool is not used.
test_connection(&self) -> Result<bool, Error>115     pub fn test_connection(&self) -> Result<bool, Error> {
116         let mut conn = self.inner.connection()?;
117 
118         let is_connected = conn.test_connected();
119 
120         #[cfg(not(feature = "pool"))]
121         conn.quit()?;
122 
123         Ok(is_connected)
124     }
125 }
126 
127 /// Contains client configuration.
128 /// Instances of this struct can be created using functions of [`SmtpTransport`].
129 #[derive(Debug, Clone)]
130 pub struct SmtpTransportBuilder {
131     info: SmtpInfo,
132     #[cfg(feature = "pool")]
133     pool_config: PoolConfig,
134 }
135 
136 /// Builder for the SMTP `SmtpTransport`
137 impl SmtpTransportBuilder {
138     /// Set the name used during EHLO
hello_name(mut self, name: ClientId) -> Self139     pub fn hello_name(mut self, name: ClientId) -> Self {
140         self.info.hello_name = name;
141         self
142     }
143 
144     /// Set the authentication mechanism to use
credentials(mut self, credentials: Credentials) -> Self145     pub fn credentials(mut self, credentials: Credentials) -> Self {
146         self.info.credentials = Some(credentials);
147         self
148     }
149 
150     /// Set the authentication mechanism to use
authentication(mut self, mechanisms: Vec<Mechanism>) -> Self151     pub fn authentication(mut self, mechanisms: Vec<Mechanism>) -> Self {
152         self.info.authentication = mechanisms;
153         self
154     }
155 
156     /// Set the timeout duration
timeout(mut self, timeout: Option<Duration>) -> Self157     pub fn timeout(mut self, timeout: Option<Duration>) -> Self {
158         self.info.timeout = timeout;
159         self
160     }
161 
162     /// Set the port to use
port(mut self, port: u16) -> Self163     pub fn port(mut self, port: u16) -> Self {
164         self.info.port = port;
165         self
166     }
167 
168     /// Set the TLS settings to use
169     #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
170     #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
tls(mut self, tls: Tls) -> Self171     pub fn tls(mut self, tls: Tls) -> Self {
172         self.info.tls = tls;
173         self
174     }
175 
176     /// Use a custom configuration for the connection pool
177     ///
178     /// Defaults can be found at [`PoolConfig`]
179     #[cfg(feature = "pool")]
180     #[cfg_attr(docsrs, doc(cfg(feature = "pool")))]
pool_config(mut self, pool_config: PoolConfig) -> Self181     pub fn pool_config(mut self, pool_config: PoolConfig) -> Self {
182         self.pool_config = pool_config;
183         self
184     }
185 
186     /// Build the transport
187     ///
188     /// If the `pool` feature is enabled an `Arc` wrapped pool is be created.
189     /// Defaults can be found at [`PoolConfig`]
build(self) -> SmtpTransport190     pub fn build(self) -> SmtpTransport {
191         let client = SmtpClient { info: self.info };
192 
193         #[cfg(feature = "pool")]
194         let client = Pool::new(self.pool_config, client);
195 
196         SmtpTransport { inner: client }
197     }
198 }
199 
200 /// Build client
201 #[derive(Debug, Clone)]
202 pub struct SmtpClient {
203     info: SmtpInfo,
204 }
205 
206 impl SmtpClient {
207     /// Creates a new connection directly usable to send emails
208     ///
209     /// Handles encryption and authentication
connection(&self) -> Result<SmtpConnection, Error>210     pub fn connection(&self) -> Result<SmtpConnection, Error> {
211         #[allow(clippy::match_single_binding)]
212         let tls_parameters = match self.info.tls {
213             #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
214             Tls::Wrapper(ref tls_parameters) => Some(tls_parameters),
215             _ => None,
216         };
217 
218         #[allow(unused_mut)]
219         let mut conn = SmtpConnection::connect::<(&str, u16)>(
220             (self.info.server.as_ref(), self.info.port),
221             self.info.timeout,
222             &self.info.hello_name,
223             tls_parameters,
224         )?;
225 
226         #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
227         match self.info.tls {
228             Tls::Opportunistic(ref tls_parameters) => {
229                 if conn.can_starttls() {
230                     conn.starttls(tls_parameters, &self.info.hello_name)?;
231                 }
232             }
233             Tls::Required(ref tls_parameters) => {
234                 conn.starttls(tls_parameters, &self.info.hello_name)?;
235             }
236             _ => (),
237         }
238 
239         if let Some(credentials) = &self.info.credentials {
240             conn.auth(&self.info.authentication, credentials)?;
241         }
242         Ok(conn)
243     }
244 }
245