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