1 use std::{ 2 fmt::{self, Debug}, 3 marker::PhantomData, 4 sync::Arc, 5 time::Duration, 6 }; 7 8 use async_trait::async_trait; 9 10 #[cfg(feature = "pool")] 11 use super::pool::async_impl::Pool; 12 #[cfg(feature = "pool")] 13 use super::PoolConfig; 14 use super::{ 15 client::AsyncSmtpConnection, ClientId, Credentials, Error, Mechanism, Response, SmtpInfo, 16 }; 17 #[cfg(feature = "async-std1")] 18 use crate::AsyncStd1Executor; 19 #[cfg(any(feature = "tokio1", feature = "async-std1"))] 20 use crate::AsyncTransport; 21 #[cfg(feature = "tokio1")] 22 use crate::Tokio1Executor; 23 use crate::{Envelope, Executor}; 24 25 /// Asynchronously sends emails using the SMTP protocol 26 #[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))] 27 pub struct AsyncSmtpTransport<E: Executor> { 28 #[cfg(feature = "pool")] 29 inner: Arc<Pool<E>>, 30 #[cfg(not(feature = "pool"))] 31 inner: AsyncSmtpClient<E>, 32 } 33 34 #[cfg(feature = "tokio1")] 35 #[async_trait] 36 impl AsyncTransport for AsyncSmtpTransport<Tokio1Executor> { 37 type Ok = Response; 38 type Error = Error; 39 40 /// Sends an email send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error>41 async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> { 42 let mut conn = self.inner.connection().await?; 43 44 let result = conn.send(envelope, email).await?; 45 46 #[cfg(not(feature = "pool"))] 47 conn.quit().await?; 48 49 Ok(result) 50 } 51 } 52 53 #[cfg(feature = "async-std1")] 54 #[async_trait] 55 impl AsyncTransport for AsyncSmtpTransport<AsyncStd1Executor> { 56 type Ok = Response; 57 type Error = Error; 58 59 /// Sends an email send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error>60 async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> { 61 let mut conn = self.inner.connection().await?; 62 63 let result = conn.send(envelope, email).await?; 64 65 conn.quit().await?; 66 67 Ok(result) 68 } 69 } 70 71 impl<E> AsyncSmtpTransport<E> 72 where 73 E: Executor, 74 { 75 /// Simple and secure transport, using TLS connections to communicate with the SMTP server 76 /// 77 /// The right option for most SMTP servers. 78 /// 79 /// Creates an encrypted transport over submissions port, using the provided domain 80 /// to validate TLS certificates. 81 #[cfg(any( 82 feature = "tokio1-native-tls", 83 feature = "tokio1-rustls-tls", 84 feature = "async-std1-native-tls", 85 feature = "async-std1-rustls-tls" 86 ))] 87 #[cfg_attr( 88 docsrs, 89 doc(cfg(any( 90 feature = "tokio1-native-tls", 91 feature = "tokio1-rustls-tls", 92 feature = "async-std1-rustls-tls" 93 ))) 94 )] relay(relay: &str) -> Result<AsyncSmtpTransportBuilder, Error>95 pub fn relay(relay: &str) -> Result<AsyncSmtpTransportBuilder, Error> { 96 use super::{Tls, TlsParameters, SUBMISSIONS_PORT}; 97 98 let tls_parameters = TlsParameters::new(relay.into())?; 99 100 Ok(Self::builder_dangerous(relay) 101 .port(SUBMISSIONS_PORT) 102 .tls(Tls::Wrapper(tls_parameters))) 103 } 104 105 /// Simple an secure transport, using STARTTLS to obtain encrypted connections 106 /// 107 /// Alternative to [`AsyncSmtpTransport::relay`](#method.relay), for SMTP servers 108 /// that don't take SMTPS connections. 109 /// 110 /// Creates an encrypted transport over submissions port, by first connecting using 111 /// an unencrypted connection and then upgrading it with STARTTLS. The provided 112 /// domain is used to validate TLS certificates. 113 /// 114 /// An error is returned if the connection can't be upgraded. No credentials 115 /// or emails will be sent to the server, protecting from downgrade attacks. 116 #[cfg(any( 117 feature = "tokio1-native-tls", 118 feature = "tokio1-rustls-tls", 119 feature = "async-std1-native-tls", 120 feature = "async-std1-rustls-tls" 121 ))] 122 #[cfg_attr( 123 docsrs, 124 doc(cfg(any( 125 feature = "tokio1-native-tls", 126 feature = "tokio1-rustls-tls", 127 feature = "async-std1-rustls-tls" 128 ))) 129 )] starttls_relay(relay: &str) -> Result<AsyncSmtpTransportBuilder, Error>130 pub fn starttls_relay(relay: &str) -> Result<AsyncSmtpTransportBuilder, Error> { 131 use super::{Tls, TlsParameters, SUBMISSION_PORT}; 132 133 let tls_parameters = TlsParameters::new(relay.into())?; 134 135 Ok(Self::builder_dangerous(relay) 136 .port(SUBMISSION_PORT) 137 .tls(Tls::Required(tls_parameters))) 138 } 139 140 /// Creates a new local SMTP client to port 25 141 /// 142 /// Shortcut for local unencrypted relay (typical local email daemon that will handle relaying) unencrypted_localhost() -> AsyncSmtpTransport<E>143 pub fn unencrypted_localhost() -> AsyncSmtpTransport<E> { 144 Self::builder_dangerous("localhost").build() 145 } 146 147 /// Creates a new SMTP client 148 /// 149 /// Defaults are: 150 /// 151 /// * No authentication 152 /// * No TLS 153 /// * A 60 seconds timeout for smtp commands 154 /// * Port 25 155 /// 156 /// Consider using [`AsyncSmtpTransport::relay`](#method.relay) or 157 /// [`AsyncSmtpTransport::starttls_relay`](#method.starttls_relay) instead, 158 /// if possible. builder_dangerous<T: Into<String>>(server: T) -> AsyncSmtpTransportBuilder159 pub fn builder_dangerous<T: Into<String>>(server: T) -> AsyncSmtpTransportBuilder { 160 let info = SmtpInfo { 161 server: server.into(), 162 ..Default::default() 163 }; 164 AsyncSmtpTransportBuilder { 165 info, 166 #[cfg(feature = "pool")] 167 pool_config: PoolConfig::default(), 168 } 169 } 170 171 /// Tests the SMTP connection 172 /// 173 /// `test_connection()` tests the connection by using the SMTP NOOP command. 174 /// The connection is closed afterwards if a connection pool is not used. test_connection(&self) -> Result<bool, Error>175 pub async fn test_connection(&self) -> Result<bool, Error> { 176 let mut conn = self.inner.connection().await?; 177 178 let is_connected = conn.test_connected().await; 179 180 #[cfg(not(feature = "pool"))] 181 conn.quit().await?; 182 183 Ok(is_connected) 184 } 185 } 186 187 impl<E: Executor> Debug for AsyncSmtpTransport<E> { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 189 let mut builder = f.debug_struct("AsyncSmtpTransport"); 190 builder.field("inner", &self.inner); 191 builder.finish() 192 } 193 } 194 195 impl<E> Clone for AsyncSmtpTransport<E> 196 where 197 E: Executor, 198 { clone(&self) -> Self199 fn clone(&self) -> Self { 200 Self { 201 inner: self.inner.clone(), 202 } 203 } 204 } 205 206 /// Contains client configuration. 207 /// Instances of this struct can be created using functions of [`AsyncSmtpTransport`]. 208 #[derive(Debug, Clone)] 209 #[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))] 210 pub struct AsyncSmtpTransportBuilder { 211 info: SmtpInfo, 212 #[cfg(feature = "pool")] 213 pool_config: PoolConfig, 214 } 215 216 /// Builder for the SMTP `AsyncSmtpTransport` 217 impl AsyncSmtpTransportBuilder { 218 /// Set the name used during EHLO hello_name(mut self, name: ClientId) -> Self219 pub fn hello_name(mut self, name: ClientId) -> Self { 220 self.info.hello_name = name; 221 self 222 } 223 224 /// Set the authentication mechanism to use credentials(mut self, credentials: Credentials) -> Self225 pub fn credentials(mut self, credentials: Credentials) -> Self { 226 self.info.credentials = Some(credentials); 227 self 228 } 229 230 /// Set the authentication mechanism to use authentication(mut self, mechanisms: Vec<Mechanism>) -> Self231 pub fn authentication(mut self, mechanisms: Vec<Mechanism>) -> Self { 232 self.info.authentication = mechanisms; 233 self 234 } 235 236 /// Set the port to use port(mut self, port: u16) -> Self237 pub fn port(mut self, port: u16) -> Self { 238 self.info.port = port; 239 self 240 } 241 242 /// Set the timeout duration timeout(mut self, timeout: Option<Duration>) -> Self243 pub fn timeout(mut self, timeout: Option<Duration>) -> Self { 244 self.info.timeout = timeout; 245 self 246 } 247 248 /// Set the TLS settings to use 249 #[cfg(any( 250 feature = "tokio1-native-tls", 251 feature = "tokio1-rustls-tls", 252 feature = "async-std1-native-tls", 253 feature = "async-std1-rustls-tls" 254 ))] 255 #[cfg_attr( 256 docsrs, 257 doc(cfg(any( 258 feature = "tokio1-native-tls", 259 feature = "tokio1-rustls-tls", 260 feature = "async-std1-rustls-tls" 261 ))) 262 )] tls(mut self, tls: super::Tls) -> Self263 pub fn tls(mut self, tls: super::Tls) -> Self { 264 self.info.tls = tls; 265 self 266 } 267 268 /// Use a custom configuration for the connection pool 269 /// 270 /// Defaults can be found at [`PoolConfig`] 271 #[cfg(feature = "pool")] 272 #[cfg_attr(docsrs, doc(cfg(feature = "pool")))] pool_config(mut self, pool_config: PoolConfig) -> Self273 pub fn pool_config(mut self, pool_config: PoolConfig) -> Self { 274 self.pool_config = pool_config; 275 self 276 } 277 278 /// Build the transport build<E>(self) -> AsyncSmtpTransport<E> where E: Executor,279 pub fn build<E>(self) -> AsyncSmtpTransport<E> 280 where 281 E: Executor, 282 { 283 let client = AsyncSmtpClient { 284 info: self.info, 285 marker_: PhantomData, 286 }; 287 288 #[cfg(feature = "pool")] 289 let client = Pool::new(self.pool_config, client); 290 291 AsyncSmtpTransport { inner: client } 292 } 293 } 294 295 /// Build client 296 pub struct AsyncSmtpClient<E> { 297 info: SmtpInfo, 298 marker_: PhantomData<E>, 299 } 300 301 impl<E> AsyncSmtpClient<E> 302 where 303 E: Executor, 304 { 305 /// Creates a new connection directly usable to send emails 306 /// 307 /// Handles encryption and authentication connection(&self) -> Result<AsyncSmtpConnection, Error>308 pub async fn connection(&self) -> Result<AsyncSmtpConnection, Error> { 309 let mut conn = E::connect( 310 &self.info.server, 311 self.info.port, 312 self.info.timeout, 313 &self.info.hello_name, 314 &self.info.tls, 315 ) 316 .await?; 317 318 if let Some(credentials) = &self.info.credentials { 319 conn.auth(&self.info.authentication, credentials).await?; 320 } 321 Ok(conn) 322 } 323 } 324 325 impl<E> Debug for AsyncSmtpClient<E> { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 327 let mut builder = f.debug_struct("AsyncSmtpClient"); 328 builder.field("info", &self.info); 329 builder.finish() 330 } 331 } 332 333 // `clone` is unused when the `pool` feature is on 334 #[allow(dead_code)] 335 impl<E> AsyncSmtpClient<E> 336 where 337 E: Executor, 338 { clone(&self) -> Self339 fn clone(&self) -> Self { 340 Self { 341 info: self.info.clone(), 342 marker_: PhantomData, 343 } 344 } 345 } 346