diff --git a/Cargo.lock b/Cargo.lock index c19c202..68af36f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,6 +123,19 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-native-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" +dependencies = [ + "futures-util", + "native-tls", + "thiserror", + "tokio", + "url", +] + [[package]] name = "async-process" version = "2.3.0" @@ -165,6 +178,7 @@ name = "async-smtp" version = "0.10.0" dependencies = [ "anyhow", + "async-native-tls", "async-std", "base64", "criterion", @@ -299,6 +313,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cc" +version = "1.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -325,6 +348,22 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "criterion" version = "0.3.6" @@ -469,6 +508,30 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.31" @@ -622,6 +685,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "itertools" version = "0.10.5" @@ -715,6 +788,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -755,12 +845,62 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pin-project" version = "1.1.7" @@ -804,6 +944,12 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "plotters" version = "0.3.7" @@ -948,6 +1094,38 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.215" @@ -990,6 +1168,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -1040,6 +1224,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -1088,6 +1285,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.41.1" @@ -1131,24 +1343,56 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "value-bag" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 9fa49bc..8eab071 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ thiserror = "1" tokio = { version = "1", default-features = false, features = ["time", "io-util"], optional = true } [dev-dependencies] +async-native-tls = { version = "0.5", default-features = false } env_logger = "^0.9" glob = "^0.3" criterion = "^0.3" @@ -39,7 +40,17 @@ name = "send" path = "examples/send.rs" required-features = ["runtime-tokio"] +[[example]] +name = "ssl-xoauth2" +path = "examples/ssl-xoauth2.rs" +required-features = ["runtime-tokio"] + +[[example]] +name = "starttls-xoauth2" +path = "examples/starttls-xoauth2.rs" +required-features = ["runtime-tokio"] + [features] default = ["runtime-tokio"] -runtime-async-std = ["async-std"] -runtime-tokio = ["tokio"] +runtime-async-std = ["async-std", "async-native-tls/runtime-async-std"] +runtime-tokio = ["tokio", "async-native-tls/runtime-tokio"] diff --git a/examples/ssl-xoauth2.rs b/examples/ssl-xoauth2.rs new file mode 100644 index 0000000..9dc978b --- /dev/null +++ b/examples/ssl-xoauth2.rs @@ -0,0 +1,43 @@ +// this example can be used as a reference for other smtp providers +// tested with gmail smtp server + +use async_native_tls::TlsConnector; +use async_smtp::authentication::{Credentials, Mechanism}; +use async_smtp::{Envelope, SendableEmail, SmtpClient, SmtpTransport}; +use tokio::io::BufStream; +use tokio::net::TcpStream; + +pub type Error = Box; +pub type Result = std::result::Result; + +// https://developers.google.com/gmail/imap/imap-smtp +const SMTP_SERVER: &str = "smtp.gmail.com"; +const SSL_PORT: u16 = 465; + +const USER: &str = "user@gmail.com"; +const ACCESS_TOKEN: &str = "accesstoken"; + +#[tokio::main] +async fn main() -> Result<()> { + let stream = TcpStream::connect((SMTP_SERVER, SSL_PORT)).await?; + let tls_stream = TlsConnector::new().connect(SMTP_SERVER, stream).await?; + + let client = SmtpClient::new(); + let mut transport = SmtpTransport::new(client, BufStream::new(tls_stream)).await?; + + let credentials = Credentials::new(USER.to_owned(), ACCESS_TOKEN.to_owned()); + transport + .auth(Mechanism::Xoauth2, &credentials) // use modern auth mechanism + .await?; + + let email = SendableEmail::new( + Envelope::new( + Some(USER.parse().unwrap()), + vec!["root@localhost".parse().unwrap()], + )?, + "Hello world", + ); + transport.send(email).await?; + + Ok(()) +} diff --git a/examples/starttls-xoauth2.rs b/examples/starttls-xoauth2.rs new file mode 100644 index 0000000..84844da --- /dev/null +++ b/examples/starttls-xoauth2.rs @@ -0,0 +1,50 @@ +// this example can be used as a reference for other smtp providers +// tested with gmail, outlook smtp servers + +use async_native_tls::TlsConnector; +use async_smtp::authentication::{Credentials, Mechanism}; +use async_smtp::{Envelope, SendableEmail, SmtpClient, SmtpTransport}; +use tokio::io::BufStream; +use tokio::net::TcpStream; + +pub type Error = Box; +pub type Result = std::result::Result; + +// https://developers.google.com/gmail/imap/imap-smtp +const SMTP_SERVER: &str = "smtp.gmail.com"; +const TLS_PORT: u16 = 587; + +const USER: &str = "user@gmail.com"; +const ACCESS_TOKEN: &str = "accesstoken"; + +#[tokio::main] +async fn main() -> Result<()> { + let stream = BufStream::new(TcpStream::connect((SMTP_SERVER, TLS_PORT)).await?); + + let client = SmtpClient::new(); + let transport = SmtpTransport::new(client, stream).await?; + + let pre_tls_stream = transport.starttls().await?.into_inner(); + let tls_stream = TlsConnector::new() + .connect(SMTP_SERVER, pre_tls_stream) + .await?; + + let client = SmtpClient::new().without_greeting(); // do not wait an greeting message after STARTTLS + let mut transport = SmtpTransport::new(client, BufStream::new(tls_stream)).await?; + + let credentials = Credentials::new(USER.to_owned(), ACCESS_TOKEN.to_owned()); + transport + .auth(Mechanism::Xoauth2, &credentials) // use modern auth mechanism + .await?; + + let email = SendableEmail::new( + Envelope::new( + Some(USER.parse().unwrap()), + vec!["root@localhost".parse().unwrap()], + )?, + "Hello world", + ); + transport.send(email).await?; + + Ok(()) +}